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

2019-12-30 19:12:15刘景俊

最近我正在处理C#中关于timeout行为的一些bug。解决方案非常有意思,所以我在这里分享给广大博友们。

我要处理的是下面这些情况:

我们做了一个应用程序,程序中有这么一个模块,它的功能向用户显示一个消息对话框,15秒后再自动关闭该对话框。但是,如果用户手动关闭对话框,则在timeout时我们无需做任何处理。 程序中有一个漫长的执行操作。如果该操作持续5秒钟以上,那么请终止这个操作。 我们的的应用程序中有执行时间未知的操作。当执行时间过长时,我们需要显示一个“进行中”弹出窗口来提示用户耐心等待。我们无法预估这次操作会持续多久,但一般情况下会持续不到一秒。为了避免弹出窗口一闪而过,我们只想要在1秒后显示这个弹出窗口。反之,如果在1秒内操作完成,则不需要显示这个弹出窗口。

这些问题是相似的。在超时之后,我们必须执行X操作,除非Y在那个时候发生。

为了找到解决这些问题的办法,我在试验过程中创建了一个类:


public class OperationHandler
{
 private IOperation _operation;
 
 public OperationHandler(IOperation operation)
 {
 _operation = operation;
 } 
 public void StartWithTimeout(int timeoutMillis)
 {
 //在超时后需要调用 "_operation.DoOperation()" 
 } 
 public void StopOperationIfNotStartedYet()
 {
 //在超时期间需要停止"DoOperation" 
 }
}

我的操作类:


public class MyOperation : IOperation
{
 public void DoOperation()
 {
 Console.WriteLine("Operation started");
 }
}
public class MyOperation : IOperation
{
 public void DoOperation()
 {
 Console.WriteLine("Operation started");
 }
}

我的测试程序:


static void Main(string[] args)
{
 var op = new MyOperation();
 var handler = new OperationHandler(op);
 Console.WriteLine("Starting with timeout of 5 seconds");
 handler.StartWithTimeout(5 * 1000);
 Thread.Sleep(6 * 1000);
 
 Console.WriteLine("Starting with timeout of 5 but cancelling after 2 seconds");
 handler.StartWithTimeout(5 * 1000);
 Thread.Sleep(2 * 1000);
 handler.StopOperationIfNotStartedYet();
 
 Thread.Sleep(4 * 1000);
 Console.WriteLine("Finished...");
 Console.ReadLine();
}

结果应该是:


Starting with timeout of 5 seconds
Operation started
Starting with timeout of 5 but cancelling after 2 seconds
Finished...

现在我们可以开始试验了!

解决方案1:在另一个线程上休眠

我最初的计划是在另一个不同的线程上休眠,同时用一个布尔值来标记Stop是否被调用。


public class OperationHandler
{
 private IOperation _operation;
 private bool _stopCalled;

 public OperationHandler(IOperation operation)
 {
 _operation = operation;
 }
 public void StartWithTimeout(int timeoutMillis)
 {
 Task.Factory.StartNew(() =>
 {
  _stopCalled = false;
  Thread.Sleep(timeoutMillis);
  if (!_stopCalled)
  _operation.DoOperation();
 });
 }
 public void StopOperationIfNotStartedYet()
 {
 _stopCalled = true;
 }
}