Warm tip: This article is reproduced from serverfault.com, please click

multithreading-如何从C#中的其他类通知线程

(multithreading - How to notify thread from other class in C#)

发布于 2020-11-27 09:55:41

我有以下代码
(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类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类StartAsync与Preproc StartAsync几乎相同。所以流程是这样的:Main->preproc.StartAsync()->PreprocessSmth(), then asynchronously SavingToDb and DoSomething, ->calc.StartAsync()->CalcSmth() then SaveDataToDb asynchronously等等...

Questioner
AzJa
Viewed
11
MickyD 2020-11-28 10:20:19

OP:

如何从C#中的其他类通知线程...以及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创建s到开车preproccalcdisplay和所有处于运行状态。我了解你想通知calcThread它可能PreprocessSmth在完成后就开始了

与老式Thread编程紧密结合的一种简单方法Windows同步对象,它是大多数操作系统的通用功能。

MSDN对此有此看法(我的重点是):

同步对象是一个对象,其手柄可以在等待函数之一指定来协调多个线程的执行多个进程可以具有同一个同步对象的句柄,从而使进程间同步成为可能。告诉我更多...

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

.NET提供了一系列类型,你可以使用这些类型来同步对共享资源的访问协调线程交互...从System.Threading.WaitHandle该类派生出多个.NET同步原语,该类封装了本机操作系统同步句柄并为线程使用了信令机制相互作用。告诉我更多...

...具体来说:

System.Threading.ManualResetEvent从中获得EventWaitHandle在发出信号时保持在信号状态,直到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
}

在我的代码中,我导出了一个新goodToGoEvent的类型变量ManualResetEvent变量同时传递给preproc.Startcalc.Start这是工作preprocSet而事件calc WaitS代表它。可以将其视为一组交通信号灯。

这是运行中的代码,方括号([ ])中显示的数字是相应的线程ID:

在此处输入图片说明

当代线程API

根据你使用/受限于的.NET版本,你可能要查看Microsoft的Task Parallel Library,Task以及如何将它们全部打包在功能区中async/await(最好是.NET 4.5+经验)。

如果我这么大胆:

无论工作是否受I / O约束,async / await是进行异步编程的最现代,最简便的方法。任务是否需要线程。免费自我推广

更多的