[英]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
是由“内”完成Task
从DoAsyncNetworkIO
。 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.