等待和任务并发

时间:2015-03-20 05:14:53

标签: c# async-await

我正在尝试创建一个TaskScheduler,它可以限制可以同时运行的线程数。我正在使用this example。问题是我看到了一些我不理解的行为。

如果我像示例那样创建我的类:

LimitedConcurrencyLevelTaskScheduler lcts = new LimitedConcurrencyLevelTaskScheduler(5);
TaskFactory factory = new TaskFactory(lcts);

然后像这样运行:

foreach (int i = 0; i < 10; ++i) 
{
    factory.StartNew(() => DoWork());
}

工作看起来像这样:

private async Task DoWork()
{
    // Do some stuff here like
    StaticIntValueWorkOne++;

    // And then more stuff that is async here
    int someValue = await DoAdditionalWorkAsync();
    Thread.Sleep(10000);
    StaticIntValueWorkTwo++;
}

我看到StaticIntValueWorkOne立即递增10次,而StaticIntValueWorkTwo只递增一次。然后在10秒后我看到StaticIntValueWorkTwo递增,然后每隔10秒递增一次。我没有得到的是DoAdditionalWorkAsync()上的await对并发性的影响。我原以为我会看到StaticIntValueWorkOne增加一次,然后StaticIntValueWorkTwo会增加一次。我错过了什么?我只需await上的factor.StartNew()吗?

2 个答案:

答案 0 :(得分:5)

  

我正在尝试创建一个TaskScheduler来限制可以同时运行的线程数。

您可能想要skip straight to the answer。 ;)

var scheduler = new ConcurrentExclusiveSchedulerPair(TaskScheduler.Default, 5)
    .ConcurrentScheduler;
  

我没有得到的是DoAdditionalWorkAsync()的await在并发中的作用。

任务计划程序仅适用于执行代码。当async方法在任务调度程序上执行时,您可以将其视为分解为多个任务,并在每个await点处中断。默认情况下,在await之后,async方法将重新输入其任务计划程序。 async方法不是&#34;在&#34;任务调度程序await

因此,当方法为await时,调度限制(一次5个)根本不适用。因此,在DoWork中,该方法将首先递增变量,然后屈服于任务调度程序。虽然屈服了,但它并没有计算出#34;对你的并发限制。之后,当该方法恢复时,它将阻塞该线程(它会&#34;计算&#34;)并增加第二个变量。

使用此代码:

private static void Main(string[] args)
{
    var scheduler = new ConcurrentExclusiveSchedulerPair(TaskScheduler.Default, 5)
        .ConcurrentScheduler;
    TaskFactory factory = new TaskFactory(scheduler);

    for (int i = 0; i < 10; ++i)
    {
        factory.StartNew(() => DoWork());
    }

    Console.ReadKey();
}

private static int StaticIntValueWorkOne, StaticIntValueWorkTwo;

private static async Task DoWork()
{
    // Do some stuff here like
    Console.WriteLine(DateTime.UtcNow + " StaticIntValueWorkOne" + Interlocked.Increment(ref StaticIntValueWorkOne));

    // And then more stuff that is async here
    await Task.Yield();
    Thread.Sleep(10000);
    Console.WriteLine(DateTime.UtcNow + " StaticIntValueWorkTwo" + Interlocked.Increment(ref StaticIntValueWorkTwo));
}

我得到了这个(预期的)输出:

3/20/2015 11:01:53 AM StaticIntValueWorkOne1
3/20/2015 11:01:53 AM StaticIntValueWorkOne5
3/20/2015 11:01:53 AM StaticIntValueWorkOne4
3/20/2015 11:01:53 AM StaticIntValueWorkOne2
3/20/2015 11:01:53 AM StaticIntValueWorkOne3
3/20/2015 11:01:53 AM StaticIntValueWorkOne6
3/20/2015 11:01:53 AM StaticIntValueWorkOne9
3/20/2015 11:01:53 AM StaticIntValueWorkOne10
3/20/2015 11:01:53 AM StaticIntValueWorkOne7
3/20/2015 11:01:53 AM StaticIntValueWorkOne8
3/20/2015 11:02:03 AM StaticIntValueWorkTwo1
3/20/2015 11:02:03 AM StaticIntValueWorkTwo3
3/20/2015 11:02:03 AM StaticIntValueWorkTwo2
3/20/2015 11:02:03 AM StaticIntValueWorkTwo4
3/20/2015 11:02:03 AM StaticIntValueWorkTwo5
3/20/2015 11:02:13 AM StaticIntValueWorkTwo6
3/20/2015 11:02:13 AM StaticIntValueWorkTwo7
3/20/2015 11:02:13 AM StaticIntValueWorkTwo8
3/20/2015 11:02:13 AM StaticIntValueWorkTwo9
3/20/2015 11:02:13 AM StaticIntValueWorkTwo10

如果要限制异步代码的并发性,请查看SemaphoreSlim或TPL数据流。

答案 1 :(得分:3)

TaskFactory.StartNew不会async知道,所以当DoWork方法到达第一个await方法并返回时,它会认为方法已完成。

await完成后,它将在捕获的SynchronizationContext上运行。

使用async-await,您可以通过实现自己的syncrhonization上下文来实现此目的,如here所示。或者,您可以使用SemaphoreSlim实现此类并发。像这样:

private SemaphoreSlim semaphore = new SemaphoreSlim(5,5);
private static int StaticIntValueWorkOne;
private static int StaticIntValueWorkTwo;

private async Task DoWork()
{
    try
    {
        await semaphore.WaitAsync();

        // Do some stuff here like
        StaticIntValueWorkOne++;
        ("StaticIntValueWorkOne " + StaticIntValueWorkOne).Dump();

        // And then more stuff that is async here
        int someValue = await DoAdditionalWorkAsync();
        await Task.Delay(10000);

        StaticIntValueWorkTwo++;
        ("StaticIntValueWorkTwo " + StaticIntValueWorkTwo).Dump();
    }
    finally
    {
        semaphore.Release();
    }
}

private async Task<int> DoAdditionalWorkAsync()
{   
   await Task.Delay(5000);
   return 0;
}

void Main()
{
    for (int i = 0; i < 10; ++i) 
    {
        DoWork();
    }
}

输出

StaticIntValueWorkOne 1
StaticIntValueWorkOne 2
StaticIntValueWorkOne 3
StaticIntValueWorkOne 4
StaticIntValueWorkOne 5
StaticIntValueWorkTwo 1
StaticIntValueWorkTwo 2
StaticIntValueWorkTwo 3
StaticIntValueWorkOne 6
StaticIntValueWorkOne 7
StaticIntValueWorkTwo 5
StaticIntValueWorkTwo 4
StaticIntValueWorkOne 8
StaticIntValueWorkOne 10
StaticIntValueWorkOne 9
StaticIntValueWorkTwo 7
StaticIntValueWorkTwo 8
StaticIntValueWorkTwo 9
StaticIntValueWorkTwo 6
StaticIntValueWorkTwo 10