如何使用任务异步地有条件地运行代码

14

我负责一个类,用于获取资源并缓存以便快速访问。该类提供了一个异步方法来获取资源:

public Task<object> GetResourceAsync(string resourceName)
{
    return Task.Factory.StartNew<object>(() =>
    {
        // look in cache

        // if not found, get from disk

        // return resource
    });
}

客户端代码如下:
myResourceProvider.GetResourceAsync("myResource")
    .ContinueWith<object>(t => Console.WriteLine("Got resource " + t.Result.ToString()));

这种方式总是使用后台线程。但是,如果在缓存中找到对象,则不希望代码异步运行。如果在缓存中找到它,我想立即返回资源而不必使用另一个线程。谢谢。
2个回答

24

.NET 4.5提供了Task.FromResult,它允许你返回一个Task<T>,但不是在线程池线程上运行委托,而是明确设置任务的返回值。

因此,在你的代码上下文中:

public Task<object> AsyncGetResource(string resourceName)
{
    object valueFromCache;
    if (_myCache.TryGetValue(resourceName, out valueFromCache)) {
        return Task.FromResult(valueFromCache);
    }
    return Task.Factory.StartNew<object>(() =>
    {
        // get from disk
        // add to cache
        // return resource
    });
}
如果您仍在使用 .NET 4.0,您可以使用 TaskCompletionSource<T> 来完成相同的事情。
var tcs = new TaskCompletionSource<object>();
tcs.SetResult(...item from cache...);
return tcs.Task;

+1 我曾经问过这个问题(https://dev59.com/q2Up5IYBdhLWcg3wXGy8),现在我知道什么时候应该使用它了。 - Royi Namir
@joe,你能否为Task.FromResult增强这段代码(添加一个解决方案)? - Royi Namir
4
默认情况下,调用 Task.Factory.StartNew 会使用 TaskScheduler.Current,在某些情况下,这可能会导致意外的行为,例如由 StartNew 启动的任务在 UI 线程上运行。如果您希望代码始终在后台线程上运行,则应始终传递 TaskScheduler.Default 或使用 Task.Run(如果可用)。 - Scott Chamberlain

2

如果您有UI连接线程,请小心。

在WPF中,非常重要的是,在UI线程(例如按钮单击事件处理程序)上使用Task.Run,以避免UI问题,并在后台线程上运行代码。

为什么?默认情况下,Task.Run是Task.Factory.StartNew的包装器,使用TaskScheduler.Default参数,而不是TaskScheduler.Current。

因此,仅仅像这样调用异步方法是不够的,因为它会在UI线程上运行并冻结它:await SomeTaskAsync();

相反,您应该在Task.Run中调用它:

  • Task.Run(async() => await SomeTaskAsync());

或者在Task.Run中使用同步方法:

  • Task.Run(() => SomeTask());

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