使用async/await进行信号量线程限制

13

我最近遇到了一个关于异步/等待调用的线程限制示例。在我的电脑上分析和测试代码后,我想出了一种稍微不同的做法。我不确定底层是否基本相同,或者是否有任何值得注意的微妙差别?

以下是基于原始示例的代码:

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创建的任务,因此更加简洁。这个描述准确吗,还是我完全错误?

1个回答

14

它并没有变得更加精简,只是稍微短了一点。通常情况下,我在 async 代码中避免使用 ContinueWith,因为 await 更加简洁,并且具有更多的 async 友好的默认语义。首先优化开发者时间,然后再优化其他方面。

您的代码会略微改变语义:在原始代码中,LongRunningTask 是从线程池上下文中执行的,而在您的代码中,则是从 CallThrottledTasks 上下文中执行的。同时,您的代码无法清晰地传播 LongRunningTask 中的异常;Task<T>.Result 将异常包装在 AggregateException 中,而 await 不会进行任何包装。


谢谢。这正是我正在寻找的:实现副作用会被忽略。感觉在异步等待中有很多这样的情况。我会研究一下异常处理。我对 TPL 中的 AggregateException 很熟悉,但还没有足够的实践经验。 - AFM
2
这实际上更多是TPL中的剩余物,它们很少与“async”一起使用,但往往只会妨碍操作。例如,Task构造函数、Start、Wait、Result、ContinueWith、WaitAll和WaitAny旨在用于并行(而不是异步)编程,并且在“async”环境中应该避免使用,除非你确实知道自己在做什么。 - Stephen Cleary
我正在查看一些视频,其中一个微妙但重要的问题是,.GetAwaiter().GetResult() 返回异常而不是包装器。他们建议使用它而不是.Result。 - hmadrigal
@hmadrigal:是的;如果必须阻塞,GetAwaiter()。GetResult()Result更好。但如果可能的话,最好使用await - Stephen Cleary

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接