我已经读过这篇文章:Is it ok to await the same task from multiple threads - is await thread safe?而我对答案并不清楚,所以这是一个特定的用例。
我有一个执行某些异步网络I / O的方法。多个线程可以同时触发此方法,我不想全部调用网络请求,如果请求已在进行中,我想阻止/等待第二个+线程,并让它们全部恢复一次IO操作已经完成。
应如何我写下面的伪代码?
我猜测每个调用线程确实需要获得自己的Task
,所以每个人都可以得到它自己的延续,所以我应该返回一个新的{{1}而不是返回currentTask
。这是由"内部"完成的。来自Task
的{{1}}。
有没有干净的方法来做到这一点,还是我必须手动滚动它?
Task
答案 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
合同。任何人都可以写自己的等待/等待,所以一定要注意:)