如何让mutliple线程等待一个单独的任务?

时间:2016-01-25 09:12:32

标签: c# multithreading asynchronous async-await

我已经读过这篇文章:Is it ok to await the same task from multiple threads - is await thread safe?而我对答案并不清楚,所以这是一个特定的用例。

我有一个执行某些异步网络I / O的方法。多个线程可以同时触发此方法,我不想全部调用网络请求,如果请求已在进行中,我想阻止/等待第二个+线程,并让它们全部恢复一次IO操作已经完成。

应如何我写下面的伪代码? 我猜测每个调用线程确实需要获得自己的Task,所以每个人都可以得到它自己的延续,所以我应该返回一个新的{{1}而不是返回currentTask。这是由"内部"完成的。来自Task的{​​{1}}。 有没有干净的方法来做到这一点,还是我必须手动滚动它?

Task

2 个答案:

答案 0 :(得分:5)

您可以使用SemaphoreSlim来确保只有一个线程实际执行后台线程。

假设您的基本任务(实际执行IO的任务)位于名为baseTask()的方法中,我将仿效:{/ p>

static async Task baseTask()
{
    Console.WriteLine("Starting long method.");
    await Task.Delay(1000);
    Console.WriteLine("Finished long method.");
}

然后您可以像这样初始化SemaphoreSlim,有点像AutoResetEvent,初始状态设置为true

static readonly SemaphoreSlim signal = new SemaphoreSlim(1, 1);

然后在调用baseTask()的方法中将调用包装到signal,以查看这是否是尝试运行baseTask()的第一个线程,如下所示:

static async Task<bool> taskWrapper()
{
    bool firstIn = await signal.WaitAsync(0);

    if (firstIn)
    {
        await baseTask();
        signal.Release();
    }
    else
    {
        await signal.WaitAsync();
        signal.Release();
    }

    return firstIn;
}

然后您的多个线程将等待taskWrapper(),而不是直接等待baseTask()

将它完全放在可编辑的控制台应用程序中:

using System;
using System.Threading;
using System.Threading.Tasks;

namespace Demo
{
    static class Program
    {
        static void Main()
        {
            for (int it = 0; it < 10; ++it)
            {
                Console.WriteLine($"\nStarting iteration {it}");
                Task[] tasks = new Task[5];

                for (int i = 0; i < 5; ++i)
                    tasks[i] = Task.Run(demoTask);

                Task.WaitAll(tasks);
            }

            Console.WriteLine("\nFinished");                  
            Console.ReadLine();
        }

        static async Task demoTask()
        {
            int id = Thread.CurrentThread.ManagedThreadId;
            Console.WriteLine($"Thread {id} starting");

            bool firstIn = await taskWrapper();

            Console.WriteLine($"Task {id}: executed: {firstIn}");
        }

        static async Task<bool> taskWrapper()
        {
            bool firstIn = await signal.WaitAsync(0);

            if (firstIn)
            {
                await baseTask();
                signal.Release();
            }
            else
            {
                await signal.WaitAsync();
                signal.Release();
            }

            return firstIn;
        }

        static async Task baseTask()
        {
            Console.WriteLine("Starting long method.");
            await Task.Delay(1000);
            Console.WriteLine("Finished long method.");
        }

        static readonly SemaphoreSlim signal = new SemaphoreSlim(1, 1);
    }
}

(这些方法都是静态的,因为它们位于控制台应用程序中;在实际代码中,它们是非静态方法。)

答案 1 :(得分:1)

DATETIME根本不一定使用延续(await类型)。即使它确实如此,你可以在一个Task.ContinueWith上有多个延续 - 它们不能同步运行(如果你有同步上下文,你可能会遇到一些问题)。

请注意,您的伪代码不是线程安全的,但您不能在锁定之外执行Task。只有currentTask = DoAsyncNetworkIO();本身是线程安全的,即便如此,只是因为您正在等待的await类以线程安全的方式实现Task合同。任何人都可以写自己的等待/等待,所以一定要注意:)