Как условно выполнить код асинхронно, используя задачи - PullRequest
13 голосов
/ 20 декабря 2011

У меня есть класс, отвечающий за поиск ресурсов, который также кэширует их для быстрого доступа. Класс предоставляет асинхронный метод для извлечения ресурса:

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 ]

22 голосов
/ 20 декабря 2011

.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;
0 голосов
/ 02 ноября 2018

Будьте осторожны, если у вас есть поток, связанный с пользовательским интерфейсом.

В WPF очень важно использовать Task.Run в потоке пользовательского интерфейса (например, обработчик события нажатия кнопки), чтобы избежать проблем пользовательского интерфейса и выполнения кода в фоновом потоке.

Почему? По умолчанию Task.Run является оболочкой для Task.Factory.StartNew с параметром TaskScheduler.Default вместо TaskScheduler.Current.

Так что недостаточно просто вызвать такой асинхронный метод, потому что он работает в потоке пользовательского интерфейса и замораживает его: await SomeTaskAsync();

Вместо этого вы должны вызвать его внутри Task.Run:

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

Или используйте метод syncron в Task.Run:

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