繁体   English   中英

如何从 C# 中的其他 class 通知线程

[英]How to notify thread from other class in C#

我有以下代码
(preproc、calc 和 display 是不同的类)

            Thread procThread = new Thread(() => preproc.Start());
            procThread.Start();
            Thread calcThread = new Thread(() => calc.Start());
            calcThread.Start();
            Thread displayThread = new Thread(() => display.Start());
            displayThread.Start();

preproc.Start()的启动方法中,我有三种方法

        lock (data)
        {
            PreprocessSmth(-10.0, 10.0);
        }
        //I want to notify next thread here that preprocess ended and now next thread can run
        //TODO: data can be calculated now
        SaveResultsToDatabase();
        DoSomethingElseThatTakesTime();

PreprocessSmth(-10.0, 10.0);之后我想通知calcThread现在可以开始了。

我正在使用lock ,但我不知道它是否能保证我想要的。

编辑:使用 TPL 我做了类似的事情。 这是好的做法,还是我搞砸了?

主要方法

Task preprocTask = preproc.StartAsync();
Task calcTask = calc.StartAsync();
Task displayTask = Task.Run(() => display.Start());
Task.WaitAll(preprocTask, calcTask, displayTask);

Preproc class StartAsync 方法内部

    private Task StartAsync()
    {
        PreprocessSmth(-10.0, 10.0);

        //TODO: data can be calculated now
        Task save = SaveResultsToDatabaseAsync();
        Task doSomething = DoSomethingElseThatTakesTimeAsync();
        return Task.WhenAll(save, doSomething);
    }

Calc class StartAsync 与 Preproc StartAsync 几乎相同。 所以流程是这样的: Main->preproc.StartAsync()->PreprocessSmth(), then asynchronously SavingToDb and DoSomething, ->calc.StartAsync()->CalcSmth() then SaveDataToDb asynchronously等等...

操作:

如何在 C# 中通知来自其他 class 的线程......以及在 PreprocessSmth(-10.0, 10.0) 之后; 我想通知 calcThread 现在可以开始了。

因此,首先进行审查。 如果我们检查您的原始代码:

Thread procThread = new Thread(() => preproc.Start());
procThread.Start();
Thread calcThread = new Thread(() => calc.Start());
calcThread.Start();
Thread displayThread = new Thread(() => display.Start());
displayThread.Start();

...我们可以看到创建了三 (3) 个Thread来驱动preproccalcdisplay ,它们都在运行的 state 中。 我了解您想通知calcThread ,它可能会在PreprocessSmth完成后开始。

与老式Thread编程相结合的一种简单方法是Windows 同步对象,这是大多数操作系统的共同特征。

MSDN 对这个主题有这样的说法(我的重点):

同步 object是一个 object 其句柄可以在一个等待函数中指定以协调多个线程的执行 多个进程可以拥有同一个同步 object 的句柄,使进程间同步成为可能。 告诉我更多...

...和 NET(我的重点):

.NET 提供了一系列类型,可用于同步对共享资源的访问或协调线程交互...多个 .NET 同步原语派生自System.Threading.WaitHandle class 封装了本机操作系统处理和使用同步的信号线程交互机制。 告诉我更多...

...具体来说:

System.Threading.ManualResetEvent ,它派生自EventWaitHandle ,并且在发出信号时停留在发出信号的 state 中,直到调用Reset方法 告诉我更多...

想到ManualResetEvent的最简单方法就像浴室水龙头 它最初可以关闭,但如果打开水龙头,它将一直保持这种状态,直到您家中的某个人将其关闭……或者当当地市议会因未付账单而切断您的供水时。

考虑到这一点,让我们将ManualResetEvent引入您的代码,如下所示:


class Program
{
    #region Statics

    static void Main(string[] args)
    {
        Logger.WriteLine($"Inside Main, thread ID is {Environment.CurrentManagedThreadId}");

        // initial state is NOT set (i.e. the "bathroom tap" is off)
        var goodToGoEvent = new ManualResetEvent(false);  // <--- NEW
        var preproc    = new PreProc();
        var procThread = new Thread(() => preproc.Start(goodToGoEvent)); // pass in event
        procThread.Start();

        var calc       = new Calc();
        var calcThread = new Thread(() => calc.Start(goodToGoEvent)); // pass in event
        calcThread.Start();

        var display       = new Display();
        var displayThread = new Thread(() => display.Start());
        displayThread.Start();

        // wait for Calc to finish
        Logger.WriteLine("Main thread - waiting for Calc thread to complete...");
        calcThread.Join();
        Logger.WriteLine("Main thread - waiting for Calc thread to complete...OK");

        Logger.WriteLine("Press any key to exit");
        Console.ReadKey();
    }

    #endregion
}

internal class Display
{
    #region Methods

    public void Start()
    {
        // NOP
    }

    #endregion
}

internal class Calc
{
    #region Methods

    /// <summary>
    ///     Starts the specified good to go event.
    /// </summary>
    /// <param name="goodToGoEvent">The event to wait on that indicates "good-to-go".</param>
    public void Start(ManualResetEvent goodToGoEvent)
    {
        Logger.WriteLine("Calc - waiting for good-to-go event to be signaled...");
        goodToGoEvent.WaitOne(); // block current thread until event is signaled
        Logger.WriteLine("Calc - waiting for good-to-go event to be signaled...OK");

        // now that PreProc is complete do what needs to be done here
    }

    #endregion
}

internal class PreProc
{
    #region Fields

    private object data = new object();

    #endregion

    #region Methods

    /// <summary>
    ///     Starts the specified good to go event.
    /// </summary>
    /// <param name="goodToGoEvent">the event to signal once processing is complete.</param>
    public void Start(ManualResetEvent goodToGoEvent)
    {
        //lock (data)  // <--- not necessary in this example unless other threads will modify data concurrently
        {
            PreprocessSmth(-10.0, 10.0);
        }

        //I want to notify next thread here that preprocess ended and now next thread can run

        Logger.WriteLine("PreProc - setting goodToGoEvent to signaled");
        goodToGoEvent.Set(); // let other threads know that preprocess has ended

        //TODO: data can be calculated now
        SaveResultsToDatabase();
        DoSomethingElseThatTakesTime();
    }

    private void DoSomethingElseThatTakesTime()
    {
        // NOP
    }

    private void PreprocessSmth(double d, double d1)
    {
        Logger.WriteLine("PreProc - Preprocessing...(sleeping 5 secs)");
        Thread.Sleep(TimeSpan.FromSeconds(5)); // simulate lengthy operation
        Logger.WriteLine("PreProc - Preprocessing...OK");
    }

    private void SaveResultsToDatabase()
    {
        // NOP
    }

    #endregion
}

public static class Logger
{
    #region Static fields

    private static readonly object Locker = new object();

    #endregion

    #region Statics

    public static void WriteLine(string message)
    {
        lock (Locker)
        {
            Console.WriteLine($"{DateTime.Now:hh:mm:ss.ff} [{Environment.CurrentManagedThreadId}] {message}");
        }
    }

    #endregion
}

在我的代码中,我引入了一个ManualResetEvent类型的新变量goodToGoEvent ,它被传递给preproc.Startcalc.Start preproc的工作是Set事件,而calc Wait它。 把它想象成一组交通信号灯。

下面是实际代码,方括号 ( [ ] ) 中显示的数字是对应的线程 ID:

在此处输入图像描述

现代线程 API

根据您使用/仅限于哪个 .NET 版本,您可能需要查看 Microsoft 的任务并行库、 Task以及它是如何用async/await封装在功能区中的(在 .NET 4.5+ 中体验最佳)。

如果我可以这么大胆:

async/await 是进行异步编程的现代且最简单的方法,无论作业是否受 I/O 限制; 任务是否需要线程。 无偿自我推销

更多的

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM