Coc*_*ut9 1 c# asynchronous task-parallel-library async-await
我正在使用 C#、TPL。我有一个类包含一些执行一些子任务的异步方法,为简单起见,我将只考虑一种方法和一个子任务:
class Test1
{
private Task SubTask() => Task.Delay(1000);
public async Task FullTask()
{
Console.WriteLine("Task Start");
await SubTask();
Console.WriteLine("Task Middle");
await SubTask();
Console.WriteLine("Task End");
}
static async Task Main()
{
Test1 Test = new Test1();
Task Task1 = Test.FullTask();
Task Task2 = Test.FullTask();
await Task.WhenAll(Task1, Task2);
}
}
Run Code Online (Sandbox Code Playgroud)
执行后,控制台中会打印以下(预期的)结果:
Task Start
Task Start
Task Middle
Task Middle
Task End
Task End
Run Code Online (Sandbox Code Playgroud)
问题是每次调用都FullTask必须在前一个调用完成后运行,如果多个调用FullTask同时发生,则必须逐个处理。我的第一个想法是使用该ContinueWith方法:
class Test2
{
private Task LastTask = Task.CompletedTask;
private Task SubTask() => Task.Delay(1000);
public Task FullTask()
{
lock(LastTask)
{
return LastTask = LastTask.ContinueWith(_ =>
{
Console.WriteLine("Task Start");
SubTask().Wait();
Console.WriteLine("Task Middle");
SubTask().Wait();
Console.WriteLine("Task End");
});
}
}
static async Task Main()
{
Test2 Test = new Test2();
Task Task1 = Test.FullTask();
Task Task2 = Test.FullTask();
await Task.WhenAll(Task1, Task2);
}
}
Run Code Online (Sandbox Code Playgroud)
同样,在执行时,以下(预期的)结果会打印在控制台中:
Task Start
Task Middle
Task End
Task Start
Task Middle
Task End
Run Code Online (Sandbox Code Playgroud)
问题是内部的 lambdaFullTask阻塞了线程,因为它使用SubTask().Wait();而不是await SubTask();. 如果Test2该类存在多个实例,每个实例都执行该FullTask方法,则会发生线程池饥饿。将 etherFullTask或 lambda(或两者)更改为 async 并不能解决问题:
class Test3
{
private Task LastTask = Task.CompletedTask;
private Task SubTask() => Task.Delay(1000);
public Task FullTask()
{
lock(LastTask)
{
return LastTask = LastTask.ContinueWith(async _ =>
{
Console.WriteLine("Task Start");
await SubTask();
Console.WriteLine("Task Middle");
await SubTask();
Console.WriteLine("Task End");
});
}
}
static async Task Main()
{
Test3 Test = new Test3();
Task Task1 = Test.FullTask();
Task Task2 = Test.FullTask();
await Task.WhenAll(Task1, Task2);
}
}
Run Code Online (Sandbox Code Playgroud)
的ContinueWith回报Task<Task>,外Task为计划任务后执行LastTask。该任务将在第一次等待时结束,并将返回将在 lambda 结束时结束的内部(编译器生成)任务。在这里,我与在外部任务到达第一个等待之前未创建的内部任务相交。因此方法不起作用。
我想要的是一种非阻塞方法,它产生与Test. 有任何想法吗?
如果您想要该ContinueWith方法的迭代(使用更现代的await),则应该使用以下方法:
private readonly object _lastTaskMutex = new object();
public Task FullTask()
{
lock (_lastTaskMutex)
return LastTask = RunAfterAsync(LastTask);
async Task RunAfterAsync(Task lastTask)
{
try
{
await lastTask;
}
catch { }
Console.WriteLine("Task Start");
await SubTask();
Console.WriteLine("Task Middle");
await SubTask();
Console.WriteLine("Task End");
}
}
Run Code Online (Sandbox Code Playgroud)
如果您只关心互斥并且确切的顺序无关紧要,那么 aSemaphoreSlim会起作用:
private readonly SemaphoreSlim _mutex = new SemaphoreSlim(1);
public async Task FullTask()
{
await _mutex.WaitAsync();
try
{
Console.WriteLine("Task Start");
await SubTask();
Console.WriteLine("Task Middle");
await SubTask();
Console.WriteLine("Task End");
}
finally
{
_mutex.Release();
}
}
Run Code Online (Sandbox Code Playgroud)
或者,如果您真正想要的是严格的 FIFO 操作队列,那么 Channel 或ActionBlockPatagonias 建议的那样是合适的;在这种情况下,您通常希望通过 aTaskCompletionSource<T>来指示单个请求何时完成:
private readonly ActionBlock<TaskCompletionSource<object>> _block = new ActionBlock<TaskCompletionSource<object>>(async tcs =>
{
try
{
Console.WriteLine("Task Start");
await SubTask();
Console.WriteLine("Task Middle");
await SubTask();
Console.WriteLine("Task End");
tcs.TrySetResult(null);
}
catch (Exception ex)
{
tcs.TrySetException(ex);
}
});
public Task FullTask()
{
var tcs = new TaskCompletionSource<object>();
_block.Post(tcs);
return tcs.Task;
}
Run Code Online (Sandbox Code Playgroud)