线程完成时通知,而不锁定调用线程

本文关键字:线程 锁定 调用 完成时 通知 | 更新日期: 2023-09-27 18:32:58

我正在开发一个基于 NET 3.5 构建的遗留应用程序。这是我无法改变的约束。我需要执行第二个线程来运行长时间运行的任务,而无需锁定 UI。线程完成后,我需要以某种方式执行回调。

现在我尝试了这个伪代码:

Thread _thread = new Thread(myLongRunningTask) { IsBackground = True };
_tread.Start();
// wait until it's done
_thread.Join();
// execute finalizer

第二个选项(不锁定 UI(如下:

Thread _thread = new Thread(myLongRunningTask) { IsBackground = True };
_tread.Start();
// wait until it's done
while(_thread.IsAlive)
{
    Application.DoEvents();
    Thread.Sleep(100);
}
// execute finalizer

当然,第二种解决方案并不好,因为它会使UI过度充电。_thread完成后执行回调的正确方法是什么?另外,我如何知道线程是否已取消或中止?

*注意:*我不能使用后台工作者,也不能使用异步库,我需要使用本机线程类。

线程完成时通知,而不锁定调用线程

这里有两种略有不同的要求:

  • 长时间运行的任务完成后执行回调
  • 运行
  • 长时间运行任务的线程完成后执行回调。

如果您对其中第一个感到满意,最简单的方法是创建一个"原始长时间运行的任务和回调"的复合任务,基本上。您甚至可以仅使用多播委托的工作方式来执行此操作:

ThreadStart starter = myLongRunningTask;
starter += () => {
    // Do what you want in the callback
};
Thread thread = new Thread(starter) { IsBackground = true };
thread.Start();

这是非常普通的,如果线程中止或引发异常,则不会触发回调。你可以把它包装在一个类中,可以有多个回调,或者是一个回调,它指定状态(中止、抛出异常等(,并通过包装原始委托、在具有 try/catch 块的方法中调用它并适当地执行回调来处理它。

除非执行任何特殊操作,否则回调将在后台线程执行,因此需要使用Control.BeginInvoke(或其他任何操作(封送回 UI 线程。

我完全理解您的要求,但您错过了一件关键的事情:您真的需要同步等待该线程的结束吗?或者,也许您只需要在检测到线程结束后执行"终结器"?

在后一种情况下,只需将调用 to myLongRunningTask包装到另一个方法中:

void surrogateThreadRoutine() {
    // try{ ..
    mytask();
    // finally { ..
    ..all 'finalization'.. or i.e. raising some Event that you'll handle elsewhere
}

并将其用作线程的例程。这样,您就会知道完成将在线程处发生,并且就在实际作业结束后。

但是,当然,如果您使用某些 UI 或其他调度程序,"完成"现在将在您的线程上运行,而不是在您的 UI 或通信框架的"普通线程"上运行。您需要确保线程任务外部的所有资源都得到适当的保护或同步,否则您可能会与其他应用程序线程发生冲突。

例如,在WinForms中,在从终结器中触摸任何UI内容之前,您将需要Control.InvokeRequired(surely=true(和Control.BeginInvoke/Invoke将上下文反弹回UI线程。

例如,在 WPF 中,在从终结器中接触任何 UI 内容之前,您将需要 Dispatcher.BeginInvoke。

或者,如果冲突可能发生在您控制的任何线程上,那么简单的适当lock()就足够了。 等等。

您可以结合使用自定义事件和BeginInvoke

public event EventHandler MyLongRunningTaskEvent;
private void StartMyLongRunningTask() {
    MyLongRunningTaskEvent += myLongRunningTaskIsDone;
    Thread _thread = new Thread(myLongRunningTask) { IsBackground = true };
    _thread.Start();
    label.Text = "Running...";
}
private void myLongRunningTaskIsDone(object sender, EventArgs arg)
{
    label.Text = "Done!";
}
private void myLongRunningTask()
{
    try 
    { 
        // Do my long task...
    } 
    finally
    {
        this.BeginInvoke(Foo, this, EventArgs.Empty);
    }
}

我检查了,它是在 .NET 3.5 下工作

您可以使用观察者模式,请看这里:

http://www.dofactory.com/Patterns/PatternObserver.aspx

观察者模式将允许您通知以前定义为观察者的其他对象。

  1. 一个非常简单的执行线程,带有完成回调
  2. 这不需要以单声道行为运行,只是为了方便起见而使用
using System;
using System.Collections.Generic;
using System.Threading;
using UnityEngine;
public class ThreadTest : MonoBehaviour
{
    private List<int> numbers = null;
    private void Start()
    {
        Debug.Log("1. Call thread task");
        StartMyLongRunningTask();
        Debug.Log("2. Do something else");
    }
    private void StartMyLongRunningTask()
    {
        numbers = new List<int>();
        ThreadStart starter = myLongRunningTask;
        starter += () =>
        {
            myLongRunningTaskDone();
        };
        Thread _thread = new Thread(starter) { IsBackground = true };
        _thread.Start();
    }
    private void myLongRunningTaskDone()
    {
        Debug.Log("3. Task callback result");
        foreach (int num in numbers)
            Debug.Log(num);
    }

    private void myLongRunningTask()
    {
        for (int i = 0; i < 10; i++)
        {
            numbers.Add(i);
            Thread.Sleep(1000);
        }
    }
}

尝试使用 ManualRestEvent 来表示线程完成。

也许使用条件变量和互斥锁,或者一些函数,如wait((,signal((,也许定时wait((不会无限阻塞主线程。

在 C# 中,这将是:

   void Notify()
{
    lock (syncPrimitive)
    {
        Monitor.Pulse(syncPrimitive);
    }
}
void RunLoop()
{
    for (;;)
    {
        // do work here...
        lock (syncPrimitive)
        {
            Monitor.Wait(syncPrimitive);
        }
    }
}

更多关于这里:条件变量 C#/.NET

这是 C# 中监视器对象的概念,您也有能够设置超时的版本

public static bool Wait(
   object obj,
   TimeSpan timeout
)

更多关于这里:https://msdn.microsoft.com/en-us/library/system.threading.monitor_methods(v=vs.110(.aspx