我正在尝试创建一个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()
吗?
答案 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