繁体   English   中英

使用LimitedConcurrencyLevelTask​​Scheduler和aync / await锁定问题

[英]Locking issue with LimitedConcurrencyLevelTaskScheduler and aync/await

我正在努力了解这个简单程序中正在发生的事情。

在下面的示例中,我有一个任务工厂,该工厂使用来自ParallelExtensionsExtras的LimitedConcurrencyLevelTask​​Scheduler,并将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完成后, awaitHttpClientGetAsync方法的其余部分安排到其上下文中。 但是,上下文将不会运行它,因为它已经有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.

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