C# 线程完成时通知,而不锁定调用线程
我正在开发一个构建在NET3.5之上的遗留应用程序。这是一个我无法改变的约束。 我需要执行第二个线程来运行长时间运行的任务,而不锁定UI。当线程完成时,我需要执行回调 现在我尝试了以下伪代码:C# 线程完成时通知,而不锁定调用线程,c#,multithreading,C#,Multithreading,我正在开发一个构建在NET3.5之上的遗留应用程序。这是一个我无法改变的约束。 我需要执行第二个线程来运行长时间运行的任务,而不锁定UI。当线程完成时,我需要执行回调 现在我尝试了以下伪代码: Thread _thread = new Thread(myLongRunningTask) { IsBackground = True }; _tread.Start(); // wait until it's done _thread.Join(); // execute finalizer 第二个
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收费过高。
当线程完成时,执行回调的正确方法是什么?另外,我如何知道线程是否被取消或中止
*注意:*我不能使用BackgroundWorker,也不能使用Async库,我需要使用本机线程类。尝试使用来发出线程完成的信号。您可以使用Observer模式,请看这里:
观察者模式将允许您通知以前定义为观察者的其他对象。这里有两种稍微不同的需求:
- 在长时间运行的任务完成后执行回调
- 一旦运行长时间运行任务的线程完成,执行回调
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线程。我完全理解您的要求,但是你错过了一件至关重要的事情:你真的需要同步等待线程的结束吗?或者,您可能只需要在检测到线程结束后执行“终结器”
在后一种情况下,只需将对myLongRunningTask
的调用包装到另一个方法中:
void surrogateThreadRoutine() {
// try{ ..
mytask();
// finally { ..
..all 'finalization'.. or i.e. raising some Event that you'll handle elsewhere
}
并将其用作线程的例程。通过这种方式,您将知道终结将发生在线程的位置,并且就在实际作业结束之后
但是,当然,如果您使用的是某些UI或其他调度程序,“终结”现在将在您的线程上运行,而不是在UI或comms框架的“正常线程”上运行。您需要确保线程任务外部的所有资源都得到了适当的保护或同步,否则可能会与其他应用程序线程发生冲突
例如,在WinForms中,在从终结器中触摸任何UI内容之前,需要使用Control.InvokeRequired(sealured=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);
}
}
我检查过了,它在.NET3.5下工作,可能使用条件变量和互斥,或者一些函数,比如wait()、signal()、timed wait(),来不无限地阻塞主线程 在C#中,这将是:
void Notify()
{
lock (syncPrimitive)
{
Monitor.Pulse(syncPrimitive);
}
}
void RunLoop()
{
for (;;)
{
// do work here...
lock (syncPrimitive)
{
Monitor.Wait(syncPrimitive);
}
}
}
更多信息请参见:
这是C#中监视器对象的概念,您还有一个可以设置超时的版本
public static bool Wait(
object obj,
TimeSpan timeout
)
更多信息请参见:
使用系统;
使用System.Collections.Generic;
使用系统线程;
使用UnityEngine;
公共类线程测试:单行为
{
私有列表编号=空;
私有void Start()
{
Log(“1.调用线程任务”);
StartMyLongRunningTask();
Debug.Log(“2.做其他事情”);
}
私有void StartMyLongRunningTask()
{
数字=新列表();
ThreadStart starter=myLongRunningTask;
初学者+=()=>
{
myLongRunningTaskDone();
};
线程_Thread=newthread(starter){IsBackground=true};
_thread.Start();
}
私有void myLongRunningTaskDone()
{
Log(“3.任务回调结果”);
foreach(整数)
Debug.Log(num);
}
私有void myLongRunningTask()
{
对于(int i=0;i<10;i++)
{
增加(i);
睡眠(1000);
}
}
}
Thread.Start(对象)可以接受用户参数。此参数可以用作状态。您是否考虑过将回调(甚至是复杂类型,如果需要)作为参数传递?您能告诉我为什么不能使用BackgroundWorker吗?我能想到的只有几个例子,细节可能很重要。我不能使用后台工作程序,因为我不能向Windows窗体.dll添加依赖项,因为异步工作程序也可以与非UI应用程序一起工作。这有什么帮助?OP需要回调机制,我不相信ManualResetEvent
提供了回调机制。只是想知道:组合/调用多播委托的顺序定义得好吗?如果没有,回调可能会在实际任务之前调用。@Caramiriel:是的,它是myLongRunningTask
保证在回调之前由executer执行。我将尝试这种方法,因为我确实需要一个委托在最后执行,这与重新执行无关
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);
}
}
}