我在.NET 3.5上有一个winforms应用程序。在此形式中,用户会触发一个操作,该操作是在另一个线程中执行的(确切的BackgroundWorker
),以免阻止UI线程。我在MVP中,因此所有这些都是由与视图接口交互的主持人完成的(由Windows表单实现)。到目前为止还不错。
我想介绍功能,从而引入超时时间以供背景操作在取消之前完成。听起来很简单。但是背景操作在第三方组件上调用了一个可能永远不会返回的功能,因此BackgroundWorker
的取消功能在这里对我无用。另外,BackgroundWorker.RunWorkerCompleted
允许我重回UI线程,因此我需要等待超时或成功,并能够回到我的调用线程(即UI线程)。
我使用普通的旧Thread
(确实支持Abort()
)和在第二个线程上运行的计时器尝试了一下,但是由于Join()
正在阻止我的UI线程,尽管描述说明说明这一点,但似乎无法使其正常工作。它将阻止"继续执行标准com和sendmessage泵送" 。诚然,我认为这意味着它将继续处理Windows消息,情况并非如此。
int timeoutInMsec = 10000;
Thread connectThread = new Thread(Connect);
Thread timerThread = new Thread(() =>
{
var timer = new System.Windows.Forms.Timer() { Interval = timeoutInMsec };
timer.Tick += (_s, _e) =>
{
timer.Stop();
if (connectThread.ThreadState == ThreadState.Running)
connectThread.Abort();
};
};
connectThread.Start();
timerThread.Start();
timerThread.Join();
connectThread.Join();
基于此问题,我尝试删除第二个计时器线程,并在计时器勾选时添加ManualResetEvent
并调用Set()
,或者当Connect方法确实完成时。在这里,我使用WaitOne
而不是Join
,但不幸的是,这也阻止了我的UI线程。我还发现了另一个问题,不幸的是,它是.NET 3.5中不可用的CancellationTokenSource
。
因此,我如何在.NET 3.5的给定时间后旋转我的工人并能够终止它执行一种OnCompleted
处理程序?
非常感谢!
ps:我在.NET中没有太多的多线程编程经验,所以很抱歉,如果这很微不足道。
如果我正确理解您的问题,则以下算法应解决您的问题:
-
和以前一样,创建一个背景工作者来完成您的背景工作。
-
在BackgroundWorker_dowork中,
- 创建一个新线程(让我们称其为"第三方线程")来调用您的第三方库,然后
- 等待第三方线程完成或超时越过。(*)
这样,您的UI将不会阻止,因为只有背景工作人员线程在等待,而不是主线程。
现在关于有趣的部分:您如何等待第三方线程完成(标有(*)的步骤)?
我的建议是简单地使用"循环等待睡眠",即(伪代码,您可以在超时使用Stopwatch
类):
do until (third-party thread has finished or x seconds have elapsed):
Thread.Sleep for 100ms
if third-party thread has not finished:
Abort it // we don't have another choice
else
Process the result
这不是最好的练习,但是很简单,它可以完成工作,您可以随时用花哨的跨线程串联的东西替换它(这是不宽容的)p>
在非GUI线程上创建Forms.Timer
是没有用的。不要在单独的线程上创建它。为什么要Joining
线程?Join
的使用是阻止当前线程直到另一个线程完成。
这是未经测试的伪代码,这是目的。
public class Form1: Form1
{
private int timeoutInMsec = 10000;
private System.Windows.Forms.Timer _timer;
private Thread _connectThread;
public Form1()
{
_connectThread = new Thread(Connect);
_connectThread.Start();
_timer = new System.Windows.Forms.Timer() { Interval = timeoutInMsec };
_timer.Tick += (_s, _e) =>
{
_timer.Stop();
if (_connectThread.ThreadState == ThreadState.Running)
_connectThread.Abort();
};
};
}
private void Connected()
{
}
private void Aborted()
{
}
private void Connect()
{
try
{
DoConnect3rdPartyStuff();
this.Invoke(Connected);
}
catch(ThreadAbortException)
{
// aborted
this.Invoke(Aborted);
}
}
}