Ответ 1
Короче говоря:
private async void ButtonClick(object sender, RoutedEventArgs e)
{
// modify UI object in UI thread
txt.Text = "started";
// run a method in another thread
await Task.Run(()=> HeavyMethod(txt));
// <<method execution is finished here>>
// modify UI object in UI thread
txt.Text = "done";
}
// This is a thread-safe method. You can run it in any thread
internal void HeavyMethod(TextBox textBox)
{
while (stillWorking)
{
textBox.Dispatcher.Invoke(() =>
{
// UI operation goes inside of Invoke
textBox.Text += ".";
});
// CPU-bound or I/O-bound operation goes outside of Invoke
System.Threading.Thread.Sleep(51);
}
}
Result:
started....................done
Объяснение:
Вы можете использовать только
await
в методеasync
.Вы можете
await
только объектawaitable
(т.е.Task
,Task<T>
илиValueTask<T>
и т.д.)Task.Run
обычно ставит в очередьTask
в пуле потоков (т.е. использует существующий поток из пула потоков или создает новый поток в пуле потоков для запуска задачи. Это все верно, если Асинхронная операция не является чистой операцией, иначе не будет никакого потока, просто чистая асинхронная операция, выполняемая ОС и драйверами устройств)Выполнение ожидает в
await
завершения задачи и возвращает ее результаты, не блокируя основной поток из-за магической способности ключевого словаasync
:Волшебство ключевого слова
async
заключается в том, что оно не создает другого потока. Это только позволяет компилятору отказаться от и вернуть контроль над этим методом.
Не путайте метод с ключевым словом
async
с методом, заключенным вTask
;Task
отвечает за потоки,async
отвечает за магию
Так
Ваш основной поток вызывает метод async
(MyButton_Click
), как обычный метод, и до сих пор без потоков... Теперь вы можете запустить задачу внутри MyButton_Click
следующим образом:
private async void MyButton_Click(object sender, RoutedEventArgs e)
{
//queue a task to run on threadpool
Task task = Task.Run(()=>
ExecuteLongProcedure(this, intParam1, intParam2, intParam3));
//wait for it to end without blocking the main thread
await task;
}
или просто
private async void MyButton_Click(object sender, RoutedEventArgs e)
{
await Task.Run(()=>
ExecuteLongProcedure(this, intParam1, intParam2, intParam3));
}
или если ExecuteLongProcedure
имеет возвращаемое значение типа string
private async void MyButton_Click(object sender, RoutedEventArgs e)
{
Task<string> task = Task.Run(()=>
ExecuteLongProcedure(this, intParam1, intParam2, intParam3));
string returnValue = await task;
}
или просто
private async void MyButton_Click(object sender, RoutedEventArgs e)
{
string returnValue = await Task.Run(()=>
ExecuteLongProcedure(this, intParam1, intParam2, intParam3));
//or in cases where you already have a "Task returning" method:
// var httpResponseInfo = await httpRequestInfo.GetResponseAsync();
}
Метод внутри задачи (или ExecuteLongProcedure
) выполняется асинхронно и выглядит следующим образом:
//change the value for the following flag to terminate the loop
bool stillWorking = true;
//calling this method blocks the calling thread
//you must run a task for it
internal void ExecuteLongProcedure(MainWindow gui, int param1, int param2, int param3)
{
//Start doing work
gui.UpdateWindow("Work Started");
while (stillWorking)
{
//put a dot in the window showing the progress
gui.UpdateWindow(".");
//the following line will block main thread unless
//ExecuteLongProcedure is called with await keyword
System.Threading.Thread.Sleep(51);
}
gui.UpdateWindow("Done and Done");
}
Типы возврата:
Предположим, у вас есть private async <ReturnType> Method() { ... }
Если <ReturnType>
- void
, вы можете только выстрелить и забыть, т.е. просто вызвать метод обычным образом: Method();
, а затем продолжить свою жизнь. Вот как WPF обрабатывает ваше событие нажатия кнопки. Если вы попытаетесь написать await Method();
, вы получите сообщение об ошибке компиляции, которое не может ждать void.
Если <ReturnType>
равен Task
, то await Method();
ничего не возвращает. Также вы можете выстрелить и забыть: Method();
.
Если <ReturnType>
равно Task<T>
, то значение, возвращаемое await Method();
, является значением типа T
. Также вы можете выстрелить и забыть: Method();
.
Итак, async
тоже делает магию:
Когда вы return
получаете значение из метода async
, оно будет заключено в Task<T>
. Итак, если вы ожидаете int
от вашего метода async
, он должен вернуть Task<int>
:
private async Task<int> GetOneAsync()
{
return 1; // return type is a simple int
// while the method signature indicates a Task<int>
}
Как мы получаем значение, заключенное в Task<>
? await
разворачивает возвращаемое значение для вас:
int number = await Task.Run(() => 1); // right hand of await is a Task<int>
// while the left hand is an int
разверните его так:
Func<int> GetOneFunc = () => 1; // synchronous function returning a number
Task<int> GetOneTask = Task.Run(GetOneFunc); // a Task<int> is started
int number = await GetOneTask; // waiting AND unwrapping Task<int> into int
Весь код:
private async Task<int> GetNumberAsync()
{
int number = await Task.Run(GetNumber); // unwrap from Task<int>
return number; // wrap in Task<int>
}
Я НЕ МОГУ В ЭТОМ ПОДЧЕРКНИТЬ! НЕ ДЕЛАЙТЕ ЭТОГО:
private async Task<int> GetNumberAsync()
{
int number = Task.Run(GetNumber).Result; // sync over async
return number;
}
Все еще в замешательстве? прочитайте асинхронные типы возврата на MSDN.
Примечание:
await
является асинхронным и отличается от task.Wait()
, который является синхронным. Но они оба делают одно и то же, ожидая завершения задачи.
await
является асинхронным и отличается от task.Result
, который является синхронным. Но оба они делают то же самое, ожидая, пока задача завершится, развернув и вернув результаты.
Чтобы иметь упакованное значение, вы всегда можете использовать Task.FromResult(1)
вместо создания нового потока с помощью Task.Run(() => 1)
.
Task.Run
является более новой (.NetFX4.5) и более простой версией Task.Factory.StartNew
Блокировка:
Связанные с процессором или связанные с IO операции, такие как Sleep
, будут блокировать основной поток, даже если они вызываются в методе с ключевым словом async
. (опять же, не путайте метод async
с методом внутри Task
. Очевидно, что это не так, если сам асинхронный метод выполняется как задача: await MyAsyncMethod
)
await
запрещает задаче блокировать основной поток, поскольку компилятор откажется от управления методом async
.
private async void Button_Click(object sender, RoutedEventArgs e)
{
Thread.Sleep(1000);//blocks
await Task.Run(() => Thread.Sleep(1000));//does not block
}
Asynchronousy is viral
Обычно, когда вы пишете асинхронный метод, вы делаете все вызывающие асинхронными до конца. Если даже один вызывающий абонент не является асинхронной операцией, вам будет лучше без асинхронности.
(это не означает, что вам нужно везде добавлять асинхронное ключевое слово!)
В WPF GUI вы начинаете с асинхронного обработчика событий до самого конца до момента, когда выполняются операции с привязкой к процессору или с вводом-выводом (задача), и до того момента, когда объекты WPF с графическим интерфейсом выполняются манипулировать (вызывать).
Соглашение об именах
Просто добавьте название метода к ключевому слову async
с помощью Async
.
Поскольку избегание методов async void
является хорошей практикой (см. шаблоны ниже), можно сказать, что только Task
возвращающие методы должны иметь постфикс с Async
.
Тип возвращаемого значения асинхронного метода должен быть
void
,Task
,Task<T>
, тип, похожий на задачу,IAsyncEnumerable<T>
илиIAsyncEnumerator<T>
WPF GUI:
Если вам нужен асинхронный доступ к GUI (внутри метода ExecuteLongProcedure
), вызовите любую операцию, которая включает в себя изменение любого не поточно-ориентированного объекта. Например, любой объект WPF GUI должен вызываться с использованием объекта Dispatcher
, который связан с потоком GUI:
void UpdateWindow(string text)
{
//safe call
Dispatcher.Invoke(() =>
{
txt.Text += text;
});
}
Однако, если задача запускается в результате изменения свойства обратного вызова property changed callback из ViewModel, нет необходимости использовать Dispatcher.Invoke
, поскольку обратный вызов фактически выполняется из потока пользовательского интерфейса.
Accessing collections on non-UI Threads
WPF позволяет получать доступ к коллекциям данных и изменять их в потоках, отличных от того, который создал коллекцию. Это позволяет использовать фоновый поток для получения данных из внешнего источника, такого как база данных, и отображения данных в потоке пользовательского интерфейса. Используя другой поток для изменения коллекции, ваш пользовательский интерфейс остается реагирующим на взаимодействие с пользователем.
Как включить межпоточный доступ
Помните, что сам метод async
выполняется в основном потоке. Так что это действительно так:
private async void MyButton_Click(object sender, RoutedEventArgs e)
{
txt.Text = "starting"; // UI Thread
await Task.Run(()=> ExecuteLongProcedure1());
txt.Text = "waiting"; // UI Thread
await Task.Run(()=> ExecuteLongProcedure2());
txt.Text = "finished"; // UI Thread
}
Patterns:
Огонь и забудь:
void Do()
{
// CPU-Bound or IO-Bound operations
}
async void DoAsync() // returns void
{
await Task.Run(Do);
}
void FireAndForget() // not blocks, not waits
{
DoAsync();
}
Стреляй и наблюдай:
Методы возврата к задачам лучше, поскольку необработанные исключения вызывают TaskScheduler.UnobservedTaskException
.
void Do()
{
// CPU-Bound or IO-Bound operations
}
async Task DoAsync() // returns Task
{
await Task.Run(Do);
}
void FireAndWait() // not blocks, not waits
{
Task.Run(DoAsync);
}
Синхронно запускать и ждать, тратя ресурсы потоков:
Это известно как Синхронизация по асинхронному режиму, это синхронная операция, но она использует более одного потока, что может вызвать голодание. Это происходит, когда вы звоните в Wait()
или пытаетесь прочитать результаты непосредственно из task.Result
до завершения задачи.
(ИЗБЕГАЙТЕ ЭТОГО РИСУНКА)
void Do()
{
// CPU-Bound or IO-Bound operations
}
async Task DoAsync() // returns Task
{
await Task.Run(Do);
}
void FireAndWait() // blocks, waits and uses 2 more threads. Yikes!
{
var task = Task.Run(DoAsync);
task.Wait();
}
Это все к этому?
Нет. Намного больше нужно узнать о async
, его контексте и его продолжении. Этот блог особенно рекомендуется.
Задача использует поток? Вы уверены?
Не обязательно. Прочитайте этот ответ, чтобы узнать больше об истинном лице async
.
Стивен Клири отлично объяснил async-await
. Он также объясняет в своем другом сообщении в блоге, когда нет темы.
Читать дальше
как к вызову-асинхронный-метод-из-синхронной-методы
Убедитесь, что вы знаете разницу между асинхронным, параллельным и параллельным.
Вы также можете прочитать простую асинхронную программу записи файлов, чтобы узнать, где вы должны одновременно работать.
Исследовать одновременное пространство имен
В конце концов, прочитайте эту электронную книгу: Patterns_of_Parallel_Programming_CSharp