线程完成时通知,而不锁定调用线程
本文关键字:线程 锁定 调用 完成时 通知 | 更新日期: 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
观察者模式将允许您通知以前定义为观察者的其他对象。
- 一个非常简单的执行线程,带有完成回调
- 这不需要以单声道行为运行,只是为了方便起见而使用
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