2015-01-18 1 views
9

Я прочитал (и использовал) async/жду довольно много в течение некоторого времени, но у меня все еще есть один вопрос, на который я не могу получить ответ. Скажем, у меня есть этот код.Как выполняется несколько задач асинхронно в потоке пользовательского интерфейса с использованием async/wait?

private async void workAsyncBtn_Click(object sender, EventArgs e) 
{ 
    var myTask = _asyncAwaitExcamples.DoHeavyWorkAsync(5); 
    await myTask; 
    statusTextBox.Text += "\r\n DoHeavyWorkAsync message"; 
} 

Он вызывается из потока пользовательского интерфейса и возвращается в поток пользовательского интерфейса. Поэтому я могу делать специфические для UI вещи в этом методе и после await myTask. Если бы я использовал .ConfigureAwait(false), я бы получил исключение потока при выполнении statusTextBox.Text += "\r\n DoHeavyWorkAsync message";, так как я бы сказал myTask, это нормально, чтобы взять любой доступный поток из пула потоков.

Мой вопрос. Насколько я понимаю, в этом случае я никогда не покидаю поток пользовательского интерфейса, но он выполняется асинхронно, пользовательский интерфейс все еще реагирует, и я могу одновременно запустить несколько Заданий и тем самым ускорить свое приложение. Как это может работать, если мы используем только один поток?

Спасибо!

EDIT для Sievajet

private async void workAsyncBtn_Click(object sender, EventArgs e) 
{ 
    await DoAsync(); 
} 

private async Task DoAsync() 
{ 
    await Task.Delay(200); 
    statusTextBox.Text += "Call to form"; 
    await Task.Delay(200); 
} 
+0

Вы конкретно говорить о marshaling работы обратно на поток пользовательского интерфейса? –

+0

Теперь я вижу, что вопрос не так специфичен, как я думал, что его можно интерпретировать по-разному. Мне стало лучше понимать ваш ответ @ Юваля Ицчакова, и после прочтения ответа Сивайета я вижу, что есть еще один ответ, когда нет связанной операции асинхронного ввода-вывода. – Andreas

+1

@ Andreas Я вижу, что вы добавили еще один пример, и он работает, потому что снова: нет нити. Вы читали в http://blog.stephencleary.com/2013/11/there-is-no-thread.html, как предложил Юваль? – Krumelur

ответ

10

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

Во-первых, я бы рекомендовал прочитать запись в блоге Стефана Клириса - There is no thread.

Для того, чтобы понять, как его можно запускать несколько единиц работы в целом, мы должны понять один важный факт: асинхронной IO оценка операции имеют (почти) ничего общего с нитками.

Как это возможно? хорошо, если мы полностью развернем всю систему до операционной системы, мы увидим, что вызовы драйверов устройств - те, которые отвечают за выполнение таких операций, как сетевые вызовы и запись на диск, были реализованы как естественно асинхронные, они не занимают нить при выполнении своей работы. Таким образом, в то время как драйвер устройства выполняет свою задачу, нет необходимости в потоке. только после того, как драйвер устройства завершит свое выполнение, он сообщит операционной системе, что это сделано через IOCP (порт завершения ввода/вывода), который затем выполнит оставшуюся часть вызова метода (это выполняется в .NET через threadpool, который имеет выделенные потоки IOCP).

Stephans блоге демонстрирует это красиво:

Going down the async rabbit hole

После того, как операционная система выполняет DPC (отложенный вызов процедуры) и в очередь IRP (I/O Request Packet), это работа не по существу делается до тех пор, драйвер устройства сигнализирует об этом с помощью . Я делаю сообщения, что приводит к выполнению цепочки операций (описанных в сообщении блога), что в конечном итоге приведет к вызову вашего кода.

Еще одна вещь, которую следует отметить, заключается в том, что .NET делает некоторые «магии» для нас за кулисами при использовании шаблона async-await. Существует такая вещь, которая называется «Контекст синхронизации» (вы можете найти довольно длинное объяснение here).Этот контекст синхронизации - это то, что требует перезапуска продолжения (код после первого await) в потоке пользовательского интерфейса (в тех местах, где такой контекст существует).

Edit:

Следует отметить, что магия с контекстом синхронизации происходит для связанных операций процессора, а также (и на самом деле для любого awaitable объекта), поэтому, когда вы используете Threadpool нить через Task.Run или Task.Factory.StartNew , это тоже будет работать.

+1

Я не думаю, что OP спрашивает о неблокирующем вводе-выводе в методе async, я думаю, он спрашивает о том, что без использования 'ConfigureAwait (false)' он может эффективно иметь несколько параллельных задач, обращающихся к текущему пользовательскому интерфейсу контекст (т.е. с использованием текущего потока пользовательского интерфейса). Ответ, вероятно, связан с тем, что одновременные задачи не обязательно выполняются на нескольких разных потоках. –

+0

@ Andreas Я, возможно, неправильно понял вопрос. Возможно, ОП может уточнить? –

+0

Думаю, я понял. Если бы мы выполнили WriteToDisk синхронизацией, мы бы начали операцию и терпеливо ждали ее возвращения. Когда мы делаем асинхронные вещи, мы начинаем операцию (независимо от ее типа), уходим и позволяем операции (которая часто не нужна нить) сообщают нам, когда это будет сделано. Правильно ли (еще упрощенно) правильно? – Andreas

4

TaskParallelLibrary (TPL) использует TaskScheduler, который может быть сконфигурирован с TaskScheduler.FromCurrentSynchronizationContext, чтобы вернуться к SynchronizationContext так:

textBox1.Text = "Start"; 
// The SynchronizationContext is captured here 
Factory.StartNew(() => DoSomeAsyncWork()) 
.ContinueWith( 
    () => 
    { 
     // Back on the SynchronizationContext it came from    
     textBox1.Text = "End"; 
    },TaskScheduler.FromCurrentSynchronizationContext()); 

Когда async метод приостановит на await, по умолчанию он будет фиксировать текущее SynchronizationContext и вывести код после ожидания в SynchronizationContext, из которого он пришел.

 textBox1.Text = "Start"; 

     // The SynchronizationContext is captured here 

     /* The implementation of DoSomeAsyncWork depends how it runs, this could run on the threadpool pool 
      or it could be an 'I/O operation' or an 'Network operation' 
      which doesnt use the threadpool */ 
     await DoSomeAsyncWork(); 

     // Back on the SynchronizationContext it came from 
     textBox1.Text = "End"; 

асинхронной и ждут пример:

async Task MyMethodAsync() 
{ 
    textBox1.Text = "Start"; 

    // The SynchronizationContext is captured here 
    await Task.Run(() => { DoSomeAsyncWork(); }); // run on the threadPool 

    // Back on the SynchronizationContext it came from 
    textBox1.Text = "End"; 
} 
+0

Хм. Mayby Я не понимаю, но в DoSomeAsyncWork я все еще могу вызвать UI-элемент (например, в приложении формы) и установить для него значения. Как это может произойти, если код сначала перенаправляется обратно в контекст? – Andreas

+0

Вы не должны это делать – Sievajet

+0

Пожалуйста, посмотрите на мое редактирование – Andreas

1

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

Отправка осуществляется с помощью SynchronizationContext, который, в свою очередь, вызывает System.Windows.Forms.Control.BeginInvoke.

CLR с помощью C# (4-е издание) (Developer Reference) 4-е издание Джеффри Рихтера страницы +749

На самом деле, Джеффри работал с MS для реализации асинхр/ОЖИДАНИЕ вдохновлен его AsyncEnumerator

Смежные вопросы

 Смежные вопросы