2017-02-06 20 views
-2

У меня есть существующая система с несколькими длительными работами. Я надеялся найти простой способ обернуть эти операции в задачи и дать пользователю сообщение о состоянии занятости.Как обновить пользовательский интерфейс winforms от отдельных задач?

Это используется .net 3.5, и я использую задний порт TPL. К сожалению, async/wait не может быть использован здесь.

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

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

private void RunTaskUpdateBefore (string msg) 
{ 
UpdateUI (msg); 
Task task = Task.Factory.StartNew (() => 
{ 
    // simulate some work being done 
    Thread.Sleep (1000); 
}); 
task.Wait(); 
} 

Я надеялся, что этот метод будет работать, но не обновляет интерфейс, пока все задачи не будут выполнены

private void ChildTaskExternalWait (string msg) 
{ 
var uiScheduler = TaskScheduler.FromCurrentSynchronizationContext(); 

Task task = Task.Factory.StartNew (() => 
{ 
    Task.Factory.StartNew (() => 
    { 
     UpdateUI (msg); 
    }, System.Threading.CancellationToken.None, TaskCreationOptions.None, uiScheduler); 

    // simulate some work being done 
    Thread.Sleep (1000); 
}); 
task.Wait(); // I assume this is the problem 
} 

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

private Task RunTaskInternalWaitOnPrevious (string msg, Task t1 = null) 
{ 
var uiScheduler = TaskScheduler.FromCurrentSynchronizationContext(); 
Task task = Task.Factory.StartNew (() => 
{ 
    if (t1 != null) 
     { 
     t1.Wait(); 
     } 
    Task.Factory.StartNew (() => 
    { 
     UpdateUI (msg); 
    }, System.Threading.CancellationToken.None, TaskCreationOptions.None, uiScheduler); 

    // simulate some work being done 
    Thread.Sleep (2000); 
}); 

return task; 
} 

Моя реализация тестовой формы:

private void button1_Click (object sender, EventArgs e) 
{ 
// Only displays the first 2-3 tasks. After that UI doesn't get updated until all are complete 
RunTaskUpdateBefore ("Task 1"); 
RunTaskUpdateBefore ("Task 2"); 
RunTaskUpdateBefore ("Task 3"); 
RunTaskUpdateBefore ("Task 4"); 
RunTaskUpdateBefore ("Task 5"); 
RunTaskUpdateBefore ("Task 6"); 
RunTaskUpdateBefore ("Task 7"); 

// this works with any number of tasks 
// Unfortunately I'm not sure if I can use it in my application (see above notes) 
//var t1 = RunTaskInternalWaitOnPrevious ("Task 1"); 
//var t2 = RunTaskInternalWaitOnPrevious ("Task 2", t1); 
// and so on 
} 
private void UpdateUI (string msg) 
    { 
    this.textBox1.Text += msg; 
    this.textBox1.Text += System.Environment.NewLine; 
    } 

обновление/уточнение

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

Задачи не централизованы для конкретной части кода и происходят повсеместно. Я не знаю, как еще описать мою ситуацию, извините.

+0

Я думаю, что в этот момент мне нужно начать с более короткого примера. – kbeal2k

ответ

2

В этом примере кода button1_Click работает синхронно и блокирует поток пользовательского интерфейса до его завершения, поэтому, несмотря на то, что Задачи, созданные в методе, который он вызывает, хотят обновить поток пользовательского интерфейса, я подозреваю, что эти попытки ставятся в очередь до тех пор, пока после того, как все закончится. К вашему моменту, ожидание и асинхронные программы были разработаны для решения этой проблемы. До того, как это было доступно, BackgroundWorkers можно было бы использовать, чтобы сделать то же самое. Я бы добавил BackgroundWorker в вашу форму, переместил весь код с button1_Click в метод BackgroundWorker DoWork и запустил фонового работника при нажатии кнопки Button1. Я считаю, что BackgroundWorkers устарели, но отлично работает с .Net 3.5

Вот краткий учебник по классу BackgroundWorker, если вы не знакомы с ним: https://www.dotnetperls.com/backgroundworker

Вставка Application.DoEvents чтобы приостановить выполнение button1_Click до UI может обновить себя, это еще один вариант, но этот подход имеет много потенциальных проблем.Смотрите эту ветку для длинного списка справедливых предупреждений: Use of Application.DoEvents()

0

При выполнении нескольких потоков, которые предоставляют информацию о ходе, который вы хотите отобразить, можно выполнить следующим образом:

  • определить класс UpdateInfo, содержащий идентификатор задачи и информация о ходе,
  • создать переменную UpdateInfos = новый список < UpdateInfo>(),
  • , когда вы хотите отправить информацию о ходе из задачи, создавать и заполнять в UpdateInfo, а затем вставьте его на список, т. е. Lock (UpdateInfos) { UpdateInfos.Add (...);}
  • создать таймер (например, каждые 250 мс) в основной форме и в событии таймера всплывают все UpdateInfo и соответственно обновляют HMI.
-1

Вы можете использовать continueWith для обновления потока сразу после выполнения задачи.

var task1 = new Task(() => Thread.Sleep(1000)); //replace with actual task 

var task2 = task1.ContinueWith(t => UpdateUI(msg), CancellationToken.None, TaskContinuationOptions.None, uiScheduler); 

task1.Start(); 
+0

TPL не является вариантом для .Net 3.5 – VMAtm

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

  • Нет связанных вопросов^_^