最近,我遇到了一个限制异步/等待调用线程的示例。在分析并使用了机器上的代码后,我想到了做相同事情的略有不同的方式。我不确定的是,引擎盖下发生的事情几乎是相同的,还是有任何细微的差异值得注意?

这是基于原始示例的代码:

private readonly SemaphoreSlim _semaphore = new SemaphoreSlim(5);

public async Task CallThrottledTasks()
{
    var tasks = new List<Task>();

    for (int count = 1; count <= 20; count++)
    {
        await _semaphore.WaitAsync();

        tasks.Add(Task.Run(async () =>
            {
                try
                {
                    int result = await LongRunningTask();
                    Debug.Print(result.ToString());
                }
                finally
                {
                    _semaphore.Release();
                }
            }));
    }

    await Task.WhenAll(tasks);

    Debug.Print("Finished CallThrottledTasks");
}

这是我对相同代码的看法:
private readonly SemaphoreSlim _semaphore = new SemaphoreSlim(5);

public async Task CallThrottledTasks()
{
    var tasks = new List<Task>();

    for (int count = 1; count <= 20; count++)
    {
        await _semaphore.WaitAsync();

        tasks.Add(LongRunningTask().ContinueWith(t =>
        {
            try
            {
                int result = t.Result;
                Debug.Print(result.ToString());
            }
            finally
            {
                _semaphore.Release();
            }
        }));
    }

    await Task.WhenAll(tasks);

    Debug.Print("Finished CallThrottledTasks");
}

我可能还很遥远,但似乎Task.Run方法正在创建一个任务以运行LongRunningTask(),然后添加了一个连续内容以打印结果,而我的方法却绕过了Task.Run创建的任务,并且更加精简因此。这是准确的还是我在这里偏离了基础?

最佳答案

它并不精简,只是一点点。通常,我避免在ContinueWith代码中使用async,因为await更加整洁,并且具有更多async友好的默认语义。首先针对开发人员时间进行优化,然后针对其他考虑因素进行优化。

您的代码确实稍微改变了语义:在原始代码中,LongRunningTask是从线程池上下文中执行的,而在您的代码中,它是从CallThrottledTasks上下文中执行的。另外,您的代码不会干净地传播LongRunningTask的异常; Task<T>.Result将异常包装在AggregateException中,而await将不进行任何包装。

10-07 12:10