Параллельное программирование с использованием TPL на WinForms
Я пытался использовать TPL на WinForms .NET 4.0, я следовал этим шагам (перейдите к концу статьи), которые предназначены для WPF, и внес некоторые небольшие изменения, чтобы он мог работать на WinForms, но он по-прежнему не работает.. Он должен отображаться результат на метке и richTextBox, но это не так... Я думаю, что параллельный процесс заставляет мышь медленно двигаться некоторое время, когда я нажимаю кнопку..
public static double SumRootN(int root)
{ double result = 0;
for (int i = 1; i < 10000000; i++)
{ result += Math.Exp(Math.Log(i) / root);}
return result;
}
private void button1_Click(object sender, EventArgs e)
{ richTextBox1.Text = "";
label1.Text = "Milliseconds: ";
var watch = Stopwatch.StartNew();
List<Task> tasks = new List<Task>();
for (int i = 2; i < 20; i++)
{ int j = i;
var t = Task.Factory.StartNew
( () =>
{ var result = SumRootN(j);
Dispatcher.CurrentDispatcher.BeginInvoke
(new Action
( () => richTextBox1.Text += "root " + j.ToString()
+ " " + result.ToString() + Environment.NewLine
)
, null
);
}
);
tasks.Add(t);
}
Task.Factory.ContinueWhenAll
( tasks.ToArray()
, result =>
{ var time = watch.ElapsedMilliseconds;
Dispatcher.CurrentDispatcher.BeginInvoke
( new Action
( () =>
label1.Text += time.ToString()
)
);
}
);
}
3 ответа
Ваш код не будет работать, потому что пользовательский интерфейс потока для отображения результата полностью отличается от WPF. В WPF пользовательский интерфейс потока - Dispatcher, а в Windows Form - другой.
Я изменил ваш код, чтобы он работал.
private void button1_Click(object sender, EventArgs e)
{
richTextBox1.Text = "";
label1.Text = "Milliseconds: ";
var watch = Stopwatch.StartNew();
List<Task> tasks = new List<Task>();
for (int i = 2; i < 20; i++)
{
int j = i;
var t = Task.Factory.StartNew(() =>
{
var result = SumRootN(j);
richTextBox1.Invoke(new Action(
() =>
richTextBox1.Text += "root " + j.ToString() + " "
+ result.ToString() + Environment.NewLine));
});
tasks.Add(t);
}
Task.Factory.ContinueWhenAll(tasks.ToArray(),
result =>
{
var time = watch.ElapsedMilliseconds;
label1.Invoke(new Action(() => label1.Text += time.ToString()));
});
}
Оставляя в стороне вопрос, было ли это хорошо делать с точки зрения обучения и упомянуто в комментариях тему "System.Windows.Threading.Dispatcher и WinForms?" с несколько запутанным ответом:
"Если вы уверены, что находитесь в потоке пользовательского интерфейса (например, в обработчике button.Click), Dispatcher.CurrentDispatcher предоставляет вам диспетчер потока пользовательского интерфейса, который вы можете использовать для отправки из фонового потока в поток пользовательского интерфейса как обычно"
уместно упомянуть, что ( также предоставил мой ответ на вышеупомянутый вопрос):
Task.Factory.StartNew()
порождает выполнение в нескольких потоках, отличных от основного пользовательского интерфейса или его дочерних потоков- можно использовать Dispatcher на любом потоке
- Приложение WPF OOTB (Out-of-thebox) обеспечивает
System.Windows.Threading.Dispatcher DispatcherObject.Dispatcher
UI потока отсутствует в формах Winfows - Используется в вопросе
Dispatcher.CurrentDispatcher
заставляет диспетчеров порождать задачи не-пользовательскими потоками
В любом случае, с дидактической точки зрения внесения минимальных изменений в исходный код WPF, вы должны были поймать и использовать диспетчер UI:
private void button1_Click(object sender, EventArgs e)
{ Dispatcher dispatcherUI = Dispatcher.CurrentDispatcher;//added **********
richTextBox1.Text = "";
label1.Text = "Milliseconds: ";
var watch = Stopwatch.StartNew();
List<Task> tasks = new List<Task>();
for (int i = 2; i < 20; i++)
{ int j = i;
var t = Task.Factory.StartNew
( () =>
{ var result = SumRootN(j);
//Dispatcher.CurrentDispatcher.BeginInvoke//***changed to
dispatcherUI.BeginInvoke
(new Action
( () => richTextBox1.Text += "root " + j.ToString()
+ " " + result.ToString() + Environment.NewLine
)
, null
);
}
);
tasks.Add(t);
}
Task.Factory.ContinueWhenAll
( tasks.ToArray()
, result =>
{ var time = watch.ElapsedMilliseconds;
//Dispatcher.CurrentDispatcher.BeginInvoke//**************changed to
dispatcherUI.BeginInvoke//added
( new Action
( () =>
label1.Text += time.ToString()
)
);
}
);
}
Как описано в приведенной ниже ссылке, правильным способом было бы полностью исключить использование класса Dispatcher. Вместо этого следует создать соответствующий экземпляр TaskScheduler и передать его методам Task. http://blogs.msdn.com/b/csharpfaq/archive/2010/06/18/parallel-programming-task-schedulers-and-synchronization-context.aspx
То есть
Task.Factory.ContinueWhenAll(tasks.ToArray(),
result =>
{
var time = watch.ElapsedMilliseconds;
this.Dispatcher.BeginInvoke(new Action(() =>
label1.Content += time.ToString()));
});
станет
var ui = TaskScheduler.FromCurrentSynchronizationContext();
Task.Factory.ContinueWhenAll(tasks.ToArray(),
result =>
{
var time = watch.ElapsedMilliseconds;
label1.Content += time.ToString();
}, CancellationToken.None, TaskContinuationOptions.None, ui);