【问题标题】:Notify when thread is complete, without locking calling thread线程完成时通知,不锁定调用线程
【发布时间】:2013-09-27 10:26: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 完成时执行回调的正确方法是什么?另外,我怎么知道线程是被取消还是中止?

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

【问题讨论】:

  • Thread.Start(object) 可以接受用户参数。该参数可以用作状态。您是否考虑过将回调(或者甚至是复杂类型,如果需要)作为参数传递?
  • 你能告诉为什么不能使用BackgroundWorker吗?我只能想到几个案例,细节实际上可能很重要。
  • 我无法使用后台工作程序,因为我无法向 Windows Form .dll 添加依赖项,因为异步工作程序也适用于非 UI 应用程序

标签: c# multithreading


【解决方案1】:

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

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

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

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

这很普通,如果线程被中止或抛出异常,回调将不会被触发。您可以将其包装在一个具有多个回调的类中,或者一个指定状态的回调(中止,抛出异常等)并通过包装原始委托来处理它,在带有try/@987654323 的方法中调用它@block 并适当地执行回调。

除非您采取任何特殊操作,否则回调将在后台线程中执行,因此您需要使用 Control.BeginInvoke(或其他)来编组回 UI 线程。

【讨论】:

  • 只是想知道:组合/调用多播委托的顺序是否明确定义?如果没有,回调可能会在实际任务之前被调用。
  • @Caramiriel:是的。 myLongRunningTask 保证在回调之前执行。
  • 我会尝试这种方法,因为我真的需要一个委托在最后执行,线程执行的结果并不重要
  • @Jaxo:() => { } 部分是一个 lambda 表达式。 += 是普通的委托组合。
  • @camino:现在你更有可能从任务开始,Task.ContinueWith 可能是你的朋友。
【解决方案2】:

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

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

【讨论】:

    【解决方案3】:

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

    【讨论】:

      【解决方案4】:

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

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

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

      【讨论】:

        【解决方案5】:
        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);
                }
            }
        }
        

        【讨论】:

          【解决方案6】:

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

          【讨论】:

          • 这有什么帮助? OP 需要一个回调机制,我不相信 ManualResetEvent 提供。
          【解决方案7】:

          也许使用条件变量和互斥锁,或者像wait()、signal()这样的函数,也许是定时的wait()来不无限阻塞主线程。

          在 C# 中,这将是:

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

          更多关于这里: Condition Variables 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

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多