[英]Locking issue with LimitedConcurrencyLevelTaskScheduler and aync/await
我正在努力了解这个简单程序中正在发生的事情。
在下面的示例中,我有一个任务工厂,该工厂使用来自ParallelExtensionsExtras的LimitedConcurrencyLevelTaskScheduler,并将maxDegreeOfParallelism设置为2。
然后,我启动2个任务,每个任务都调用一个异步方法(例如,一个异步Http请求),然后获取等待者和完成任务的结果。
问题似乎是Task.Delay(2000)
从未完成。 如果我将maxDegreeOfParallelism设置为3(或更大),它将完成。 但是在maxDegreeOfParallelism = 2(或更小)的情况下,我的猜测是没有可用的线程来完成任务。 这是为什么?
它似乎与async / await有关,因为如果我删除它并简单地在DoWork
执行Task.Delay(2000).GetAwaiter().GetResult()
,它就可以完美地工作。 异步/等待是以某种方式使用父任务的任务计划程序,或者它是如何连接的?
using System;
using System.Linq;
using System.Threading.Tasks;
using System.Threading.Tasks.Schedulers;
namespace LimitedConcurrency
{
class Program
{
static void Main(string[] args)
{
var test = new TaskSchedulerTest();
test.Run();
}
}
class TaskSchedulerTest
{
public void Run()
{
var scheduler = new LimitedConcurrencyLevelTaskScheduler(2);
var taskFactory = new TaskFactory(scheduler);
var tasks = Enumerable.Range(1, 2).Select(id => taskFactory.StartNew(() => DoWork(id)));
Task.WaitAll(tasks.ToArray());
}
private void DoWork(int id)
{
Console.WriteLine($"Starting Work {id}");
HttpClientGetAsync().GetAwaiter().GetResult();
Console.WriteLine($"Finished Work {id}");
}
async Task HttpClientGetAsync()
{
await Task.Delay(2000);
}
}
}
预先感谢您的任何帮助
默认情况下 , await
捕获当前上下文并将其用于恢复async
方法。 该上下文为SynchronizationContext.Current
,除非为null
,否则为TaskScheduler.Current
。
在这种情况下, await
正捕获LimitedConcurrencyLevelTaskScheduler
用于执行DoWork
。 因此,在两次启动Task.Delay
之后,这两个线程都被阻止(由于GetAwaiter().GetResult()
)。 Task.Delay
完成后, await
将HttpClientGetAsync
方法的其余部分安排到其上下文中。 但是,上下文将不会运行它,因为它已经有2个线程。
因此,您最终会在上下文中阻塞线程,直到它们的async
方法完成,但是直到上下文中有空闲线程, async
方法才能完成。 因此陷入僵局。 与死锁的标准“不阻止异步代码”样式非常相似,只是使用n个线程而不是一个线程。
说明:
问题似乎是Task.Delay(2000)从未完成。
Task.Delay
正在完成,但是await
无法继续执行async
方法。
如果我将maxDegreeOfParallelism设置为3(或更大),它将完成。 但是在maxDegreeOfParallelism = 2(或更小)的情况下,我的猜测是没有可用的线程来完成任务。 这是为什么?
有很多可用的线程。 但是LimitedConcurrencyTaskScheduler
仅允许2个线程在其上下文中运行。
它似乎与async / await有关,因为如果我删除它并简单地在DoWork中执行Task.Delay(2000).GetAwaiter()。GetResult(),它就可以完美地工作。
是; 捕获上下文的正是await
。 Task.Delay
不捕获上下文内部,所以它可以无需输入完整的LimitedConcurrencyTaskScheduler
。
解:
通常,任务计划程序在异步代码上不能很好地工作。 这是因为任务计划程序是为并行任务而不是异步任务而设计的。 因此,它们仅在代码正在运行 (或被阻止)时适用。 在这种情况下, LimitedConcurrencyLevelTaskScheduler
仅“计算”正在运行的代码。 如果您有一个正在await
的方法,则不会根据该并发限制来“计数”。
所以,你的代码已经结束了的情况下它具有同步过异步反模式,可能是因为有人试图避免的问题, await
如预期有限的并发任务调度不灵。 然后,此异步同步模式导致了死锁问题。
现在,您可以通过在各处使用ConfigureAwait(false)
来添加更多黑客手段,并继续阻止异步代码, 或者可以对其进行更好地修复。
一个更合适的解决方法是进行异步限制。 完全抛出LimitedConcurrencyLevelTaskScheduler
; 并发限制任务调度程序仅适用于同步代码,并且您的代码是异步的。 您可以使用SemaphoreSlim
进行异步限制,如下所示:
class TaskSchedulerTest
{
private readonly SemaphoreSlim _mutex = new SemaphoreSlim(2);
public async Task RunAsync()
{
var tasks = Enumerable.Range(1, 2).Select(id => DoWorkAsync(id));
await Task.WhenAll(tasks);
}
private async Task DoWorkAsync(int id)
{
await _mutex.WaitAsync();
try
{
Console.WriteLine($"Starting Work {id}");
await HttpClientGetAsync();
Console.WriteLine($"Finished Work {id}");
}
finally
{
_mutex.Release();
}
}
async Task HttpClientGetAsync()
{
await Task.Delay(2000);
}
}
我认为您遇到了同步死锁。 您正在等待线程完成,而该线程正在等待线程完成。 永远不会发生。 如果使DoWork方法异步,则可以等待HttpClientGetAsync()调用,从而避免死锁。
using MassTransit.Util;
using System;
using System.Linq;
using System.Threading.Tasks;
//using System.Threading.Tasks.Schedulers;
namespace LimitedConcurrency
{
class Program
{
static void Main(string[] args)
{
var test = new TaskSchedulerTest();
test.Run();
}
}
class TaskSchedulerTest
{
public void Run()
{
var scheduler = new LimitedConcurrencyLevelTaskScheduler(2);
var taskFactory = new TaskFactory(scheduler);
var tasks = Enumerable.Range(1, 2).Select(id => taskFactory.StartNew(() => DoWork(id)));
Task.WaitAll(tasks.ToArray());
}
private async Task DoWork(int id)
{
Console.WriteLine($"Starting Work {id}");
await HttpClientGetAsync();
Console.WriteLine($"Finished Work {id}");
}
async Task HttpClientGetAsync()
{
await Task.Delay(2000);
}
}
}
https://medium.com/rubrikkgroup/understanding-async-avoiding-deadlocks-e41f8f2c6f5d
TLDR永远不会调用.result,我确定是.GetResult();。 正在做
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.