2014-01-16 3 views
1

Я хотел попробовать Threading.Task (C#), чтобы выполнить некоторую работу параллельно. В этом простом примере у меня есть форма с индикатором выполнения и кнопкой. При щелчке вызывается функция RunParallel. Без Task.WaitAll() он, кажется, проходит через штраф. Однако с выражением WaitAll форма показывает и ничего не происходит. Я не понимаю, что я делаю неправильно в настройке ниже. Спасибо заранее.Task.WaitAll freezes app C#

public partial class MainWindow : Form 
{ 
    public delegate void BarDelegate(); 
    public MainWindow() 
    { 
    InitializeComponent(); 
    } 
    private void button_Click(object sender, EventArgs e) 
    { 
     RunParallel(); 
    } 
    private void RunParallel() { 
     int numOfTasks = 8; 
     progressBar1.Maximum = numOfTasks; 
     progressBar1.Minimum = 0; 
     try 
     { 
      List<Task> allTasks = new List<Task>(); 
      for (int i = 0; i < numOfTasks; i++) 
      { 
       allTasks.Add(Task.Factory.StartNew(() => { doWork(i); })); 
      } 
      Task.WaitAll(allTasks.ToArray()); 
     } 
     catch { } 
    } 
    private void doWork(object o1) 
    { 
     // do work... 
     // then 
     this.Invoke(new BarDelegate(UpdateBar)); 
    } 
    private void UpdateBar() 
    { 
     if (progressBar1.Value < progressBar1.Maximum) progressBar1.Value++; 
    } 
} 
+1

Task.WaitAll является операцией блокировки. Используйте ContinueWith или ContinueWhenAll вместо этого – Dimitri

+1

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

ответ

0

RunParallel блокирует выполнение всех заданий. Используйте другой механизм для уведомления пользовательского интерфейса.

12

Вы ожидаете изменения в пользовательском интерфейсе. Это замораживает пользовательский интерфейс. Не делай этого.

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

Мой совет: используйте async/await, если это возможно.

И , пожалуйста, не проглатывайте исключения. Вы создаете будущую работу для себя именно так.

+7

+1 для «пожалуйста, не проглатывайте исключения». –

+0

Спасибо за ваш ответ. Не могли бы вы привести пример async/wait? – timkado

+0

Я мог бы предоставить только очень короткую консультацию в этом месте. Лучше направить вас на https://www.google.com/webhp?complete=1&hl=ru#complete=1&hl=ru&q=c%23+winforms+await, чтобы у вас была более полная информация. – usr

3

Это то, что WaitAll делает, оно блокируется, пока все задачи не закончатся. Эти задачи не могут закончить, потому что Invoke будет работать его действие в потоке пользовательского интерфейса, который уже заблокирован WaitAll

Чтобы сделать код действительно работать асинхронно, попробовать что-то вроде этого:

private void RunParallel() { 
    int numOfTasks = 8; 
    progressBar1.Maximum = numOfTasks; 
    progressBar1.Minimum = 0; 
    try 
    { 
     var context=TaskScheduler.FromCurrentSynchronizationContext() 
     for (int i = 0; i < numOfTasks; i++) 
     { 
      Task.Factory.StartNew(()=>DoWork(i)) 
        .ContinueWith(()=>UpdateBar(),context); 
     } 

    } 
    catch (Exception exc) 
    { 
     MessageBox.Show(exc.ToString(),"AAAAAARGH"); 
    } 
} 
private void DoWork(object o1) 
{ 
    // do work... 

} 
private void UpdateBar() 
{ 
    if (progressBar1.Value < progressBar1.Maximum) progressBar1.Value++; 
} 

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

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

Используя async/await в .NET 4.5+, вы можете написать это более простым способом. Следующее выполнит DoWork в фоновом режиме без блокировки, но все же обновляет пользовательский интерфейс каждый раз, когда заканчивается задание.

private async void button1_Click(object sender, EventArgs e) 
{ 
     int numOfTasks = 8; 
     progressBar1.Maximum = numOfTasks; 
     progressBar1.Minimum = 0; 
     try 
     { 
      for (int i = 0; i < numOfTasks; i++) 
      { 
       await Task.Run(() => DoWork(i)); 
       UpdateBar(); 
      } 

     } 
     catch (Exception exc) 
     { 
      MessageBox.Show(exc.ToString(), "AAAAAARGH"); 
     } 
    } 

await сообщает компилятор генерировать код для выполнения ничего под ним ОНТ оригинала (UI) threadwhen задачи ее правая отделка:

+0

Хороший ответ, но я действительно не могу продвигать рассказчику «catch (Exception)», а затем продолжать, как будто ничего не случилось. –

+0

Никто не говорит новичкам «продолжать, как будто ничего не случилось». На самом деле, я говорю, что это не производственный код **. Что касается того, что вы будете делать, это зависит от сценария, который я думаю: в настольном приложении вы говорите пользователю, регистрируетесь и, вероятно, пытаетесь восстановить или разрешить приложению умирать. Возможно, на серверном приложении вы не возвращаете результатов, но регистрируете проблему и отправляете предупреждение администратору. В демонстрационном приложении, позволяющем приложению сбой или показывая его программисту, достаточно, чтобы он мог исправить код и повторить попытку –

+0

Спасибо Panagiotis! Пример C# 4.5 отлично работает. Однако после цикла for я хочу дождаться завершения каждой задачи. Как мне это сделать, чтобы реструктурировать? – timkado

1

В DoWork вы называете this.Invoke(...), который ждет потока пользовательского интерфейса для обработки сообщений , К несчастью, вы, поток UI, не обрабатываете сообщения, потому что он ждет завершения всех doWork(...).

Самое простое исправить - это изменить this.Invoke на this.BeginInvoke (он будет отправлять сообщения, но не дождаться их обработки) в doWork. Хотя, я должен администратор, он по-прежнему не является книгой, так как пользовательский интерфейс не должен ждать ничего. Простой шаблон (предварительно асинхронной/ждут эры):

Task.Factory.StartNew(() => { 
    ... work ... 
}) 
.ContinueWith((t) => { 
    ... updating UI (if needed) ... 
}, TaskScheduler.FromCurrentSynchronizationContext()); 
Смежные вопросы