C# Language
Контекст синхронизации в Async-Await
Поиск…
Псевдокод для ключевых слов async / await
Рассмотрим простой асинхронный метод:
async Task Foo()
{
Bar();
await Baz();
Qux();
}
Упрощение, мы можем сказать, что этот код на самом деле означает следующее:
Task Foo()
{
Bar();
Task t = Baz();
var context = SynchronizationContext.Current;
t.ContinueWith(task) =>
{
if (context == null)
Qux();
else
context.Post((obj) => Qux(), null);
}, TaskScheduler.Current);
return t;
}
Это означает, что async
слова async
/ await
используют текущий контекст синхронизации, если он существует. Т.е. вы можете написать код библиотеки, который будет корректно работать в пользовательских интерфейсах, веб-и консольных приложениях.
Отключение синхронизации
Чтобы отключить контекст синхронизации, вы должны вызвать метод ConfigureAwait
:
async Task() Foo()
{
await Task.Run(() => Console.WriteLine("Test"));
}
. . .
Foo().ConfigureAwait(false);
ConfigureAwait предоставляет средства для предотвращения поведения по умолчанию SynchronizationContext; передача false для параметра flowContext запрещает использование SynchronizationContext для возобновления выполнения после ожидания.
Цитата из It's All About SynchronizationContext .
Почему SynchronizationContext так важен?
Рассмотрим этот пример:
private void button1_Click(object sender, EventArgs e)
{
label1.Text = RunTooLong();
}
Этот метод заморозит приложение пользовательского интерфейса до тех пор, пока RunTooLong
не будет завершен. Приложение будет неактуальным.
Вы можете попробовать выполнить внутренний код асинхронно:
private void button1_Click(object sender, EventArgs e)
{
Task.Run(() => label1.Text = RunTooLong());
}
Но этот код не будет выполняться, потому что внутреннее тело может быть запущено на не-UI-потоке и не должно напрямую изменять свойства пользовательского интерфейса :
private void button1_Click(object sender, EventArgs e)
{
Task.Run(() =>
{
var label1Text = RunTooLong();
if (label1.InvokeRequired)
lable1.BeginInvoke((Action) delegate() { label1.Text = label1Text; });
else
label1.Text = label1Text;
});
}
Теперь не забудьте всегда использовать этот шаблон. Или попробуйте SynchronizationContext.Post
, который сделает это для вас:
private void button1_Click(object sender, EventArgs e)
{
Task.Run(() =>
{
var label1Text = RunTooLong();
SynchronizationContext.Current.Post((obj) =>
{
label1.Text = label1 Text);
}, null);
});
}