Я хотел бы спросить вас о вашем мнении о правильной архитектуре, когда использовать Task.Run
. Я испытываю медленный пользовательский интерфейс в нашем приложении WPF .NET 4.5 (с платформой Caliburn Micro).
В основном я делаю (очень упрощенные фрагменты кода):
public class PageViewModel : IHandle<SomeMessage>
{
...
public async void Handle(SomeMessage message)
{
ShowLoadingAnimation();
// Makes UI very laggy, but still not dead
await this.contentLoader.LoadContentAsync();
HideLoadingAnimation();
}
}
public class ContentLoader
{
public async Task LoadContentAsync()
{
await DoCpuBoundWorkAsync();
await DoIoBoundWorkAsync();
await DoCpuBoundWorkAsync();
// I am not really sure what all I can consider as CPU bound as slowing down the UI
await DoSomeOtherWorkAsync();
}
}
Из статей / видео, которые я прочитал / увидел, я знаю, что await
async
это не обязательно работает в фоновом потоке, и чтобы начать работу в фоновом режиме, нужно обернуть его с помощью await Task.Run(async () => ... )
. Использование async
await
не блокирует пользовательский интерфейс, но, тем не менее, он работает в потоке пользовательского интерфейса, что делает его медленным.
Где лучше всего разместить Task.Run?
Должен ли я просто
Оберните внешний вызов, потому что это менее трудоемкая работа для .NET
, или я должен обернуть только связанные с процессором методы, которые выполняются внутри,
Task.Run
поскольку это делает его многоразовым для других мест? Я не уверен, что начинать работу с фоновыми потоками в ядре - хорошая идея.
Объявление (1), первое решение будет выглядеть так:
public async void Handle(SomeMessage message)
{
ShowLoadingAnimation();
await Task.Run(async () => await this.contentLoader.LoadContentAsync());
HideLoadingAnimation();
}
// Other methods do not use Task.Run as everything regardless
// if I/O or CPU bound would now run in the background.
Объявление (2), второе решение будет выглядеть так:
public async Task DoCpuBoundWorkAsync()
{
await Task.Run(() => {
// Do lot of work here
});
}
public async Task DoSomeOtherWorkAsync(
{
// I am not sure how to handle this methods -
// probably need to test one by one, if it is slowing down UI
}
источник
await Task.Run(async () => await this.contentLoader.LoadContentAsync());
должна быть простоawait Task.Run( () => this.contentLoader.LoadContentAsync() );
. AFAIK вы ничего не получите, добавив секундуawait
иasync
внутриTask.Run
. А так как вы не передаете параметры, это немного упрощаетawait Task.Run( this.contentLoader.LoadContentAsync );
.Ответы:
Обратите внимание на рекомендации по выполнению работы в пользовательском интерфейсе , собранные в моем блоге:
Есть две техники, которые вы должны использовать:
1) Используйте,
ConfigureAwait(false)
когда можете.Например,
await MyAsync().ConfigureAwait(false);
вместоawait MyAsync();
.ConfigureAwait(false)
сообщает,await
что вам не нужно возобновлять работу в текущем контексте (в данном случае «в текущем контексте» означает «в потоке пользовательского интерфейса»). Однако для остальной части этогоasync
метода (послеConfigureAwait
) вы не можете делать ничего, что предполагает, что вы находитесь в текущем контексте (например, обновлять элементы пользовательского интерфейса).Для получения дополнительной информации см. Мою статью MSDN Лучшие практики в асинхронном программировании .
2) Используйте
Task.Run
для вызова связанных с процессором методов.Вы должны использовать
Task.Run
, но не внутри кода, который вы хотите использовать повторно (например, код библиотеки). Таким образом, вы используетеTask.Run
для вызова метода, а не как часть реализации метода.Так что чисто работа с CPU будет выглядеть так:
Который вы бы назвали, используя
Task.Run
:Методы, которые являются смесью привязки к процессору и вводу-выводу, должны иметь
Async
подпись с документацией, указывающей на их привязку к процессору:Который вы бы также назвали используя
Task.Run
(так как он частично связан с процессором):источник
ConfigureAwait(false)
. Если вы сделаете это сначала, то вы можете обнаружить, чтоTask.Run
это совершенно не нужно. Если вам все еще нужноTask.Run
, это не имеет большого значения для времени выполнения в этом случае, если вы вызываете его один или несколько раз, так что просто делайте то, что наиболее естественно для вашего кода.ConfigureAwait(false)
метод с привязкой к процессору, это все-таки поток пользовательского интерфейса будет выполнять метод с привязкой к процессору, и только все последующее может быть выполнено в потоке TP. Или я что-то не так понял?Task.Run
понимает асинхронные подписи, поэтому он не будет завершен, пока неDoWorkAsync
будет завершен. Дополнительныйasync
/await
не нужен. Я объясняю больше "почему" в моей серии блогов наTask.Run
этикет .TaskCompletionSource<T>
или один из его сокращенной записи обозначений , таких какFromAsync
. У меня есть запись в блоге, в которой более подробно объясняется, почему асинхронные методы не требуют потоков .Одна проблема с вашим ContentLoader заключается в том, что внутри он работает последовательно. Лучшим примером является распараллеливание работы, а затем синхронизация в конце, поэтому мы получаем
Очевидно, что это не сработает, если какая-либо из задач требует данных из других более ранних задач, но должно дать вам лучшую общую пропускную способность для большинства сценариев.
источник