2015-08-12 7 views
1

Во время создания простого примера с использованием async/await я обнаружил, что некоторые примеры просто иллюстрируют шаблон на Button1_Click как методы и свободно обновляют элементы управления графическим интерфейсом непосредственно из методов async. Поэтому можно считать это безопасным механизмом. Но мой тестовый код постоянно рушился на TargetInvocationException исключениях в mscorlib.dll с внутренними исключениями, такими как: , ArgumentOutOfRange и т. Д. Что касается трассировки стека, все, казалось, указывало на метки, отображающие результаты (и управляемые непосредственно из методов async, привязанных к кнопке обработчики событий). Сбой, по-видимому, исправляется при использовании старой школы Control.Invoke при доступе к элементам управления графическим интерфейсом.Обновление GUI из метода async

Вопросы: Я пропустил что-то важное? Используются ли методы асинхронизации так же, как и работники нитей/фона, ранее использовавшиеся для долгосрочных операций, и поэтому рекомендуемое решение Invoke? Являются ли фрагменты кода, управляющие графическим интерфейсом напрямую с async, неправильными?

example

EDIT: Для недостающего исходного downvoters: создать простую форму, содержащую три кнопки и один StatusStrip, содержащие две метки ...

//#define OLDSCHOOL_INVOKE 

using System; 
using System.Threading.Tasks; 
using System.Windows.Forms; 

namespace AsyncTests 
{ 
    public partial class Form1 : Form 
    { 
     public Form1() 
     { 
      InitializeComponent(); 
     } 


     private async void LongTermOp() 
     { 
      int delay; 
      int thisId; 

      lock (mtx1) 
      { 
       delay = rnd.Next(2000, 10000); 
       thisId = firstCount++; 
#if OLDSCHOOL_INVOKE 
       Invoke(new Action(() => 
#endif 
       label1Gen.Text = $"Generating first run delay #{thisId} of {delay} ms" 
#if OLDSCHOOL_INVOKE 
       )) 
#endif 
       ; 
       ++firstPending; 
      } 

      await Task.Delay(delay); 

      lock (mtx1) 
      { 
       --firstPending; 
#if OLDSCHOOL_INVOKE 
       Invoke(new Action(() => 
#endif 
       label1Gen.Text = $"First run #{thisId} completed, {firstPending} pending..." 
#if OLDSCHOOL_INVOKE 
       )) 
#endif 
       ; 
      } 
     } 


     private async Task LongTermOpAsync() 
     { 
      await Task.Run((Action)LongTermOp); 
     } 

     private readonly Random rnd = new Random(); 
     private readonly object mtx1 = new object(); 
     private readonly object mtx2 = new object(); 
     private int firstCount; 
     private int firstPending; 
     private int secondCount; 
     private int secondPending; 

     private async void buttonRound1_Click(object sender, EventArgs e) 
     { 
      await LongTermOpAsync(); 
     } 

     private async void buttonRound2_Click(object sender, EventArgs e) 
     { 
      await Task.Run(async() => 
      { 
       int delay; 
       int thisId; 

       lock (mtx2) 
       { 
        delay = rnd.Next(2000, 10000); 
        thisId = secondCount++; 
#if OLDSCHOOL_INVOKE 
        Invoke(new Action(() => 
#endif 
        label2Gen.Text = $"Generating second run delay #{thisId} of {delay} ms" 
#if OLDSCHOOL_INVOKE 
        )) 
#endif 
        ; 
        ++secondPending; 
       } 
       await Task.Delay(delay); 
       lock (mtx2) 
       { 
        --secondPending; 
#if OLDSCHOOL_INVOKE 
        Invoke(new Action(() => 
#endif 
        label2Gen.Text = $"Second run #{thisId} completed, {secondPending} pending..." 
#if OLDSCHOOL_INVOKE 
        )) 
#endif 
        ; 
       } 
      });    
     } 

     private void buttonRound12_Click(object sender, EventArgs e) 
     { 
      buttonRound1_Click(sender, e); 
      buttonRound2_Click(sender, e); 
     } 


     private bool isRunning = false; 

     private async void buttonCycle_Click(object sender, EventArgs e) 
     { 
      isRunning = !isRunning; 

      await Task.Run(() => 
      { 
       while (isRunning) 
       { 
        buttonRound12_Click(sender, e); 
        Application.DoEvents(); 
       } 
      }); 
     } 
    } 
} 
+0

Вы можете обновлять пользовательский интерфейс только из потока, создавшего пользовательский интерфейс. –

+0

Будет сложно получить точный ответ на вашу ситуацию, не публикуя код нарушения. –

+0

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

ответ

6

Ни Task ни await даст вам никаких гарантий в этом отношении. Вам нужно рассмотреть контекст, в котором была создана задача, и где было продолжено продолжение.

Если вы используете await в обработчике событий winforms, то синхронизируется контекст, и продолжение возвращается обратно в поток пользовательского интерфейса (на самом деле он довольно много звонит Invoke по данному блоку кода). Однако, если вы только начинаете новую задачу с Task.Run или используете await из другого контекста синхронизации, это больше не применяется. Решение состоит в том, чтобы запустить продолжение в соответствующем планировщике задач, который вы можете получить из контекста синхронизации winforms.

Однако следует отметить, что это все еще не обязательно означает, что события async будут работать должным образом. Например, Winforms также использует события для таких вещей, как CellPainting, где он фактически зависит от того, что они работают синхронно. Если вы используете await в таком случае, он почти гарантированно не работает должным образом - продолжение будет по-прежнему отправляться в поток пользовательского интерфейса, но это не обязательно делает его безопасным. Например, предположим, что элемент управления имеет такой код:

using (var graphics = NewGraphics()) 
{ 
    foreach (var cell in cells) 
    CellPainting(cell, graphics); 
} 

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

Не менее важно, чтобы код мог зависеть от изменения кода: например, есть события, в которых вы установили какое-то значение в своем EventArgs, чтобы указать, например. успех или дать некоторое возвращаемое значение.Опять же, это означает, что вы не можете использовать await внутри - насколько известно абоненту, функция просто вернулась в тот момент, когда вы делаете await (если только он не синхронно завершает работу).

+0

Спасибо, что прочитали мои вопросы ... – sharpener

+0

Обманчивая простота async/ждет во всей красе. –

0

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

Посмотрите здесь: SO Question

+2

с использованием метода 'async' не мешает вам быть на Пользовательский интерфейс. И без кода из OP мы можем только рассуждать о том, какова была истинная причина его ошибки. – sstan