C#中的多线程超时处理实践

发表于:2018-3-06 09:29

字体: | 上一篇 | 下一篇 | 我要投稿

 作者:阿子    来源:博客园

分享:
  新的改进需求
  假设我们现在可以连续多次调用StartWithTimeout(),而不是等待第一个超时完成后调用。
  但是这里的预期行为是什么?实际上存在以下几种可能性:
  在以前的StartWithTimeout超时期间调用StartWithTimeout时:忽略第二次启动。
  在以前的StartWithTimeout超时期间调用StartWithTimeout时:停止初始话Start并使用新的StartWithTimeout。
  在以前的StartWithTimeout超时期间调用StartWithTimeout时:在两个启动中调用DoOperation。 在StopOperationIfNotStartedYet中停止所有尚未开始的操作(在超时时间内)。
  在以前的StartWithTimeout超时期间调用StartWithTimeout时:在两个启动中调用DoOperation。 在StopOperationIfNotStartedYet停止一个尚未开始的随机操作。
  可能性1可以通过Timer和ManualResetEvent可以轻松实现。 事实上,我们已经在我们的Timer解决方案中涉及到了这个。
  public void StartWithTimeout(int timeoutMillis)
  {
  if (_timer != null)
  return;
  ...
  public void StartWithTimeout(int timeoutMillis)
  {
  if (_timer != null)
  return;
  ...
  }
  可能性2也可以很容易地实现。 这个地方请允许我卖个萌,代码自己写哈^_^
  可能性3不可能通过使用Timer来实现。 我们将需要有一个定时器的集合。 一旦停止操作,我们需要检查并处理定时器集合中的所有子项。 这种方法是可行的,但通过ManualResetEvent我们可以非常简洁和轻松的实现这一点!
  可能性4跟可能性3相似,可以通过定时器的集合来实现。
  可能性3:使用单个ManualResetEvent停止所有操作
  让我们了解一下这里面遇到的难点:
  假设我们调用StartWithTimeout 10秒超时。
  1秒后,我们再次调用另一个StartWithTimeout,超时时间为10秒。
  再过1秒后,我们再次调用另一个StartWithTimeout,超时时间为10秒。
  预期的行为是这3个操作会依次10秒、11秒和12秒后启动。
  如果5秒后我们会调用Stop(),那么预期的行为就是所有正在等待的操作都会停止, 后续的操作也无法进行。
  我稍微改变下Program.cs,以便能够测试这个操作过程。 这是新的代码:
class Program
{
static void Main(string[] args)
{
var op = new MyOperation();
var handler = new OperationHandler(op);
Console.WriteLine("Starting with timeout of 10 seconds, 3 times");
handler.StartWithTimeout(10 * 1000);
Thread.Sleep(1000);
handler.StartWithTimeout(10 * 1000);
Thread.Sleep(1000);
handler.StartWithTimeout(10 * 1000);
Thread.Sleep(13 * 1000);
Console.WriteLine("Starting with timeout of 10 seconds 3 times, but cancelling after 5 seconds");
handler.StartWithTimeout(10 * 1000);
Thread.Sleep(1000);
handler.StartWithTimeout(10 * 1000);
Thread.Sleep(1000);
handler.StartWithTimeout(10 * 1000);
Thread.Sleep(5 * 1000);
handler.StopOperationIfNotStartedYet();
Thread.Sleep(8 * 1000);
Console.WriteLine("Finished...");
Console.ReadLine();
}
}
  下面就是使用ManualResetEvent的解决方案:
public class OperationHandler
{
private IOperation _operation;
private ManualResetEvent _mre = new ManualResetEvent(false);
public OperationHandler(IOperation operation)
{
_operation = operation;
}
public void StartWithTimeout(int timeoutMillis)
{
Task.Factory.StartNew(() =>
{
bool wasStopped = _mre.WaitOne(timeoutMillis);
if (!wasStopped)
_operation.DoOperation();
});
}
public void StopOperationIfNotStartedYet()
{
Task.Factory.StartNew(() =>
{
_mre.Set();
Thread.Sleep(10);//This is necessary because if calling Reset() immediately, not all waiting threads will 'proceed'
_mre.Reset();
});
}
}
  输出结果跟预想的一样:
  Starting with timeout of 10 seconds, 3 times
  Operation started
  Operation started
  Operation started
  Starting with timeout of 10 seconds 3 times, but cancelling after 5 seconds
  Finished...
  很开森对不对?
  当我检查这段代码时,我发现Thread.Sleep(10)是必不可少的,这显然超出了我的意料。 如果没有它,除3个等待中的线程之外,只有1-2个线程正在进行。 很明显的是,因为Reset()发生得太快,第三个线程将停留在WaitOne()上。
  可能性4:单个AutoResetEvent停止一个随机操作
  假设我们调用StartWithTimeout 10秒超时。1秒后,我们再次调用另一个StartWithTimeout,超时时间为10秒。再过1秒后,我们再次调用另一个StartWithTimeout,超时时间为10秒。然后我们调用StopOperationIfNotStartedYet()。
  目前有3个操作超时,等待启动。 预期的行为是其中一个被停止, 其他2个操作应该能够正常启动。
  我们的Program.cs可以像以前一样保持不变。 OperationHandler做了一些调整:
public class OperationHandler
{
private IOperation _operation;
private AutoResetEvent _are = new AutoResetEvent(false);
public OperationHandler(IOperation operation)
{
_operation = operation;
}
public void StartWithTimeout(int timeoutMillis)
{
_are.Reset();
Task.Factory.StartNew(() =>
{
bool wasStopped = _are.WaitOne(timeoutMillis);
if (!wasStopped)
_operation.DoOperation();
});
}
public void StopOperationIfNotStartedYet()
{
_are.Set();
}
}
  执行结果是:
  Starting with timeout of 10 seconds, 3 times
  Operation started
  Operation started
  Operation started
  Starting with timeout of 10 seconds 3 times, but cancelling after 5 seconds
  Operation started
  Operation started
  Finished...
  结语
  在处理线程通信时,超时后继续执行某些操作是常见的应用。我们尝试了一些很好的解决方案。一些解决方案可能看起来不错,甚至可以在特定的流程下工作,但是也有可能在代码中隐藏着致命的bug。当这种情况发生时,我们应对时需要特别小心。
  AutoResetEvent和ManualResetEvent是非常强大的类,我在处理线程通信时一直使用它们。这两个类非常实用。正在跟线程通信打交道的朋友们,快把它们加入到项目里面吧!
上文内容不用于商业目的,如涉及知识产权问题,请权利人联系博为峰小编(021-64471599-8017),我们将立即处理。
22/2<12
《2023软件测试行业现状调查报告》独家发布~

关注51Testing

联系我们

快捷面板 站点地图 联系我们 广告服务 关于我们 站长统计 发展历程

法律顾问:上海兰迪律师事务所 项棋律师
版权所有 上海博为峰软件技术股份有限公司 Copyright©51testing.com 2003-2024
投诉及意见反馈:webmaster@51testing.com; 业务联系:service@51testing.com 021-64471599-8017

沪ICP备05003035号

沪公网安备 31010102002173号