16

我正在开发一个基于 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 完成时执行回调的正确方法是什么?另外,我怎么知道线程是否被取消或中止?

*注意:*我不能使用 BackgroundWorker,也不能使用 Async 库,我需要使用原生线程类。

4

7 回答 7

42

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

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

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

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 线程。

于 2013-09-27T10:33:17.470 回答
1

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

在后一种情况下,只需将调用包装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()就足够了。等等

于 2013-09-27T10:41:57.950 回答
1

您可以结合使用自定义事件和使用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 下工作

于 2013-09-27T10:57:08.627 回答
0

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

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

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

于 2013-09-27T10:33:06.950 回答
0
  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);
        }
    }
}
于 2019-09-29T19:36:41.697 回答
-1

尝试使用ManualRestEvent来发出线程完成的信号。

于 2013-09-27T10:32:54.553 回答
-1

也许使用条件变量和互斥锁,或者像 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# 中 Monitor 对象的概念,您也有可以设置超时的版本

public static bool Wait(
   object obj,
   TimeSpan timeout
)

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

于 2016-08-10T09:34:48.713 回答