在给定时间后等待并终止线程而不阻塞.NET 3.5

Waiting for and terminating a thread after a given time without blocking in .NET 3.5

我有一个 .NET 3.5 上的 WinForms 应用程序。在这种形式下,用户触发一个在另一个线程(准确地说是 BackgroundWorker)中执行的操作,以免阻塞 UI 线程。我在 MVP 中,所以所有这一切都是由与视图界面交互的演示者完成的(由 Windows 表单实现)。到目前为止一切顺利。

我想介绍一种功能,即为后台操作引入超时时间,以便在取消操作之前完成。听起来很简单。但是后台操作调用第三方组件上的单个函数可能永远不会 return,所以 BackgroundWorker 的取消功能在这里对我没有用。此外,BackgroundWorker.RunWorkerCompleted 允许我回到 UI 线程,所以我需要等待超时或成功并能够回到我的调用线程(即 UI线程)。

我在第二个线程上使用普通的旧 Thread(确实支持 Abort())和定时器 运行 进行了尝试,但似乎无法正常工作完全正确,因为 Join() 阻塞了我的 UI 线程,尽管描述表明它将阻塞 "while continuing to perform standard COM and SendMessage pumping"。诚然,我认为这意味着它将继续处理 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();

基于 , I tried removing the second timer thread and adding a ManualResetEvent and calling Set() when the timer ticked, or when the Connect method did indeed complete. Here, instead of Join I used WaitOne, but unfortunately this also blocks my UI thread. I also found this other question,不幸的是 CancellationTokenSource 在 .NET 3.5 中不可用。

那么,如何在 .NET 3.5 中启动我的 worker 并能够在给定的时间后终止它,同时又能够返回到我启动 worker 的线程线程执行某种 OnCompleted 处理程序?

非常感谢!

PS: 我在 .NET 中的多线程编程方面经验不多,所以如果这很琐碎,我很抱歉。

在非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);
        }       
    }   
}

如果我正确理解了你的问题,下面的算法应该可以解决你的问题:

  • 和以前一样,创建一个 BackgroundWorker 来做你的后台工作。

  • 在BackgroundWorker_DoWork,

    • 创建一个新线程(我们称之为"third-party thread")来调用您的第三方库,然后
    • 等待第三方线程完成或超时结束。 (*)

这样,您的 UI 就不会阻塞,因为 只有 Backgroundworker 线程 在等待,而不是主线程。

现在谈谈有趣的部分:如何等待第三方线程完成(标有 (*) 的步骤)?

我的建议是简单地使用"loop waiting with sleep",即(伪代码,你可以使用Stopwatch class超时):

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

这不是最佳实践,但它很简单,它可以完成工作,并且一旦一切正常,您总是可以用花哨的跨线程同步东西(这很重要)替换它。