Как условно выполнить код асинхронно с помощью задач
У меня есть класс, отвечающий за поиск ресурсов, который также кэширует их для быстрого доступа. Класс предоставляет асинхронный метод для извлечения ресурса:
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 ответа
.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;
Будьте осторожны, если у вас есть пользовательский интерфейс, связанный потоком.
В 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());