Warning: file_get_contents(/data/phpspider/zhask/data//catemap/6/multithreading/4.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C# 线程完成时通知,而不锁定调用线程_C#_Multithreading - Fatal编程技术网

C# 线程完成时通知,而不锁定调用线程

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 第二个

我正在开发一个构建在NET3.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收费过高。 当线程完成时,执行回调的正确方法是什么?另外,我如何知道线程是否被取消或中止


*注意:*我不能使用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
)
更多信息请参见:

  • 一个带有完成回调的非常简单的执行线程
  • 这不需要在mono行为中运行,只是为了方便使用
  • 使用系统;
    使用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);
            }
        }
    }