简体   繁体   English

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

[英]How to have mutliple threads await a single Task?

I've read this: Is it ok to await the same task from multiple threads - is await thread safe? 我读过这个: 从多个线程等待相同的任务是否可以 - 等待线程安全吗? and I don't feel clear about the answer, so here's a specific use case. 我不清楚答案,所以这是一个特定的用例。

I have a method that performs some async network I/O. 我有一个执行一些异步网络I / O的方法。 Multiple threads can hit this method at once, and I dont wan't them all to invoke a network request, If a request is already in progress I want to block/await the 2nd+ threads, and have them all resume once the single IO operation has completed. 多个线程可以同时触发此方法,我不希望它们全部调用网络请求,如果请求已在进行中我想阻止/等待第二个+线程,并且一旦单个IO操作就让它们全部恢复已经完成了。

How should I write the following pseudcode? 应该怎么写下面的伪代码? I'm guessing each calling thread really needs to get its own Task , so each can get it's own continuation, so instead of returning currentTask I should return a new Task which is completed by the "inner" Task from DoAsyncNetworkIO . 我猜每个调用线程确实需要获得自己的Task ,所以每个人都可以得到它自己的延续,所以不是返回currentTask我应该回到一个新的Task是由“内”完成TaskDoAsyncNetworkIO Is there a clean way to do this, or do I have to hand roll it? 有没有干净的方法来做到这一点,还是我必须手动滚动它?

static object mutex = new object();
static Task currentTask;

async Task Fetch()
{
    lock(mutex)
    {
        if(currentTask != null)
            return currentTask;
    }

    currentTask = DoAsyncNetworkIO();
    await currentTask;

    lock(mutex)
    {
        var task = currentTask;
        currentTask = null;
        return task;
    }
}

You could use a SemaphoreSlim to ensure that only one thread actually executes the background thread. 您可以使用SemaphoreSlim来确保只有一个线程实际执行后台线程。

Assume your base task (the one actually doing the IO) is in a method called baseTask() , which I shall emulate like so: 假设您的基本任务(实际执行IO的那个)在一个名为baseTask()的方法中,我将仿效:

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

Then you can initialise a SemaphoreSlim like so, to act a bit like an AutoResetEvent with initial state set to true : 然后你可以像这样初始化一个SemaphoreSlim ,有点像AutoResetEvent ,初始状态设置为true

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

Then wrap the call to baseTask() in a method that checks signal to see if this is the first thread to try to run baseTask() , like so: 然后在一个方法中调用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;
}

Then your multiple threads would await taskWrapper() rather than awaiting baseTask() directly. 然后你的多个线程将等待taskWrapper()而不是直接等待baseTask()

Putting that altogether in a compilable console application: 将它完全放在可编辑的控制台应用程序中:

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);
    }
}

(The methods are all static because they are in a console app; in real code they would be non-static methods.) (这些方法都是静态的,因为它们位于控制台应用程序中;在实际代码中,它们是非静态方法。)

await doesn't necessarily use continuations (the Task.ContinueWith kind) at all. await根本不一定使用continuation( Task.ContinueWith kind)。 Even when it does, you can have multiple continuations on one Task - they just can't all run synchronously (and you might run into some issues if you have a synchronization context). 即使它确实如此,您也可以在一个Task上拥有多个延续 - 它们不能全部同步运行(如果您有同步上下文,则可能会遇到一些问题)。

Do note that your pseudo-code isn't thread-safe, though - you can't just do currentTask = DoAsyncNetworkIO(); 请注意,您的伪代码不是线程安全的 - 您不能只执行currentTask = DoAsyncNetworkIO(); outside of a lock. 在锁外。 Only the await itself is thread-safe, and even then, only because the Task class that you're awaiting implements the await contract in a thread-safe way. 只有await本身是线程安全的,即便如此,只是因为你正在等待的Task类以线程安全的方式实现await契约。 Anyone can write their own awaiter/awaitable, so make sure to pay attention :) 任何人都可以写自己的等待/等待,所以一定要注意:)

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

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