C# 线程完成时通知,不锁定调用线程
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/19048492/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Notify when thread is complete, without locking calling thread
提问by Raffaeu
I am working on a legacy application that is built on top of NET 3.5. This is a constraint that I can't change. I need to execute a second thread to run a long running task without locking the UI. When the thread is complete, somehow I need to execute a Callback.
我正在开发一个建立在 NET 3.5 之上的遗留应用程序。这是我无法改变的约束。我需要执行第二个线程来运行长时间运行的任务而不锁定 UI。当线程完成时,我需要以某种方式执行回调。
Right now I tried this pseudo-code:
现在我尝试了这个伪代码:
Thread _thread = new Thread(myLongRunningTask) { IsBackground = True };
_tread.Start();
// wait until it's done
_thread.Join();
// execute finalizer
The second option, which does not lock the UI, is the following:
不锁定 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
Of course the second solution is not good cause it overcharge the UI. What is the correct way to execute a callback when a _thread is complete? Also, how do I know if the thread was cancelled or aborted?
当然,第二种解决方案不好,因为它对 UI 收费过高。_thread 完成时执行回调的正确方法是什么?另外,我怎么知道线程是被取消还是中止了?
*Note: *I can't use the BackgroundWorker and I can't use the Async library, I need to work with the native thread class.
*注意: *我不能使用BackgroundWorker 也不能使用Async 库,我需要使用本机线程类。
采纳答案by Jon Skeet
There are two slightly different kinds of requirement here:
这里有两种稍微不同的要求:
- Execute a callback once the long-running task has completed
- Execute a callback once the thread in which the long-running task was running has completed.
- 长时间运行的任务完成后执行回调
- 一旦运行长时间运行的任务的线程完成,就执行回调。
If you're happy with the first of these, the simplest approach is to create a compound task of "the original long-running task, and the callback", basically. You can even do this just using the way that multicast delegates work:
如果您对第一个感到满意,最简单的方法是创建一个“原始长期运行任务和回调”的复合任务,基本上。您甚至可以仅使用多播委托的工作方式来做到这一点:
ThreadStart starter = myLongRunningTask;
starter += () => {
// Do what you want in the callback
};
Thread thread = new Thread(starter) { IsBackground = true };
thread.Start();
That's very vanilla, and the callback won't be fired if the thread is aborted or throws an exception. You could wrap it up in a class with either multiple callbacks, or a callback which specifies the status (aborted, threw an exception etc) and handles that by wrapping the original delegate, calling it in a method with a try
/catch
block and executing the callback appropriately.
这是非常普通的,如果线程中止或抛出异常,则不会触发回调。您可以将它包装在一个具有多个回调的类中,或者一个指定状态(中止、抛出异常等)的回调中,并通过包装原始委托、在带有try
/catch
块的方法中调用它并执行回调来处理它适当地。
Unless you take any special action, the callback will be executed inthe background thread, so you'll need to use Control.BeginInvoke
(or whatever) to marshal back to the UI thread.
除非您采取任何特殊操作,否则回调将在后台线程中执行,因此您需要使用Control.BeginInvoke
(或其他方式)将其编组回 UI 线程。
回答by Vasiliy
Try to use ManualRestEventto signal of thread complete.
尝试使用ManualRestEvent来表示线程完成。
回答by Xavjer
You could use the Observer Pattern, take a look here:
你可以使用观察者模式,看看这里:
http://www.dofactory.com/Patterns/PatternObserver.aspx
http://www.dofactory.com/Patterns/PatternObserver.aspx
The observer pattern will allow you, to notify other objects which were previously defined as observer.
观察者模式将允许您通知以前定义为观察者的其他对象。
回答by quetzalcoatl
I absolutely understand your requirements, but you've missed one crucial thing: do you reallyneed to wait for the end of that thread synchronously? Or maybe you just need to execute the "finalizer" after thread's end is detected?
我完全理解您的要求,但是您错过了一件至关重要的事情:您真的需要同步等待该线程结束吗?或者您可能只需要在检测到线程结束后执行“终结器”?
In the latter case, simply wrap the call to myLongRunningTask
into another method:
在后一种情况下,只需将调用包装myLongRunningTask
到另一个方法中:
void surrogateThreadRoutine() {
// try{ ..
mytask();
// finally { ..
..all 'finalization'.. or i.e. raising some Event that you'll handle elsewhere
}
and use it as the thread's routine. That way, you'll know that the finalization will occur at the thread's and, just after the end of the actual job.
并将其用作线程的例程。这样,您就会知道最终确定将发生在线程和实际作业结束之后。
However, of course, if you're with some UI or other schedulers, the "finalization" will now run on yours thread, not on the "normal threads" of your UI or comms framework. You will need to ensure that all resources are external to your thread-task are properly guarded or synchronized, or else you'll probably clash with other application threads.
但是,当然,如果您使用某些 UI 或其他调度程序,那么“最终确定”现在将在您的线程上运行,而不是在您的 UI 或通信框架的“普通线程”上运行。您需要确保线程任务外部的所有资源都得到适当保护或同步,否则您可能会与其他应用程序线程发生冲突。
For instance, in WinForms, before you touch any UI things from the finalizer, you will need the Control.InvokeRequired (surely=true) and Control.BeginInvoke/Invoke to bounce the context back to the UI thread.
例如,在 WinForms 中,在您从终结器接触任何 UI 事物之前,您将需要 Control.InvokeRequired (surely=true) 和 Control.BeginInvoke/Invoke 将上下文反弹回 UI 线程。
For instance, in WPF, before you touch any UI things from the finalizer, you will need the Dispatcher.BeginInvoke..
例如,在 WPF 中,在您从终结器接触任何 UI 内容之前,您将需要 Dispatcher.BeginInvoke..
Or, if the clash could occur with any threads you control, simple proper lock()
could be enough. etc.
或者,如果您控制的任何线程都可能发生冲突,那么简单适当lock()
就足够了。等等。
回答by AxFab
You can use a combination of custom event and the use of BeginInvoke
:
您可以结合使用自定义事件和使用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);
}
}
I checked, it's work under .NET 3.5
我检查过,它在 .NET 3.5 下工作
回答by Micha? Ziobro
Maybe using conditional variables and mutex, or some functions like wait(), signal(), maybe timed wait() to not block main thread infinitely.
也许使用条件变量和互斥锁,或者像wait()、signal()这样的函数,也许定时wait()不会无限阻塞主线程。
In C# this will be:
在 C# 中,这将是:
void Notify()
{
lock (syncPrimitive)
{
Monitor.Pulse(syncPrimitive);
}
}
void RunLoop()
{
for (;;)
{
// do work here...
lock (syncPrimitive)
{
Monitor.Wait(syncPrimitive);
}
}
}
more on that here: Condition Variables C#/.NET
更多关于这里: 条件变量 C#/.NET
It is the concept of Monitor object in C#, you also have version that enables to set timeout
这是C#中Monitor对象的概念,你也有可以设置超时的版本
public static bool Wait(
object obj,
TimeSpan timeout
)
more on that here: https://msdn.microsoft.com/en-us/library/system.threading.monitor_methods(v=vs.110).aspx
更多相关信息:https: //msdn.microsoft.com/en-us/library/system.threading.monitor_methods(v=vs.110).aspx
回答by Mark Day
- A very simple thread of execution with completion callback
- This does not need to run in a mono behavior and is simply used for convenience
- 一个带有完成回调的非常简单的执行线程
- 这不需要以单声道行为运行,只是为了方便而使用
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);
}
}
}