Ответ 1
Правильный способ состоял бы в том, чтобы сконструировать ваши компоненты синхронно и выполнить целую цепочку в фоновом потоке.
Как-то я не могу поверить, что я первый, кто столкнулся с этой проблемой (и я не хочу верить, что я достаточно глуп, чтобы не видеть решение напрямую), но мой поиск-фу не был достаточно сильный.
Я регулярно сталкиваюсь с ситуацией, когда мне нужно сделать несколько трудоемких шагов один за другим. Рабочий процесс выглядит как
var data = DataGetter.GetData();
var processedData = DataProcessor.Process(data);
var userDecision = DialogService.AskUserAbout(processedData);
// ...
Я не хочу блокировать пользовательский интерфейс на каждом шаге, поэтому каждый метод немедленно возвращается и вызывает событие после его завершения. Теперь начинается веселье, поскольку вышеупомянутый блок кода мутирует в
DataGetter.Finished += (data) =>
{
DataProcessor.Finished += (processedData) =>
{
DialogService.Finished(userDecision) =>
{
// ....
}
DialogService.AskUserAbout(processedData);
}
DataProcessor.Process(data);
};
DataGetter.GetData();
Это очень похоже на стиль продолжения прохождения для моего вкуса, и должен быть лучший способ структурировать этот код. Но как?
Правильный способ состоял бы в том, чтобы сконструировать ваши компоненты синхронно и выполнить целую цепочку в фоновом потоке.
Параллельная библиотека задач может быть полезна для такого кода. Обратите внимание, что TaskScheduler.FromCurrentSynchronizationContext() может использоваться для запуска задачи в потоке пользовательского интерфейса.
Task<Data>.Factory.StartNew(() => GetData())
.ContinueWith(t => Process(t.Result))
.ContinueWith(t => AskUserAbout(t.Result), TaskScheduler.FromCurrentSynchronizationContext());
Вы можете поместить все в BackgroundWorker. Следующий код будет работать только правильно, если вы измените методы GetData, Process и AskUserAbout для запуска синхронно.
Что-то вроде этого:
private BackgroundWorker m_worker;
private void StartWorking()
{
if (m_worker != null)
throw new InvalidOperationException("The worker is already doing something");
m_worker = new BackgroundWorker();
m_worker.CanRaiseEvents = true;
m_worker.WorkerReportsProgress = true;
m_worker.ProgressChanged += worker_ProgressChanged;
m_worker.DoWork += worker_Work;
m_worker.RunWorkerCompleted += worker_Completed;
}
private void worker_Work(object sender, DoWorkEventArgs args)
{
m_worker.ReportProgress(0, "Getting the data...");
var data = DataGetter.GetData();
m_worker.ReportProgress(33, "Processing the data...");
var processedData = DataProcessor.Process(data);
// if this interacts with the GUI, this should be run in the GUI thread.
// use InvokeRequired/BeginInvoke, or change so this question is asked
// in the Completed handler. it safe to interact with the GUI there,
// and in the ProgressChanged handler.
m_worker.ReportProgress(67, "Waiting for user decision...");
var userDecision = DialogService.AskUserAbout(processedData);
m_worker.ReportProgress(100, "Finished.");
args.Result = userDecision;
}
private void worker_ProgressChanged(object sender, ProgressChangedEventArgs args)
{
// this gets passed down from the m_worker.ReportProgress() call
int percent = args.ProgressPercentage;
string progressMessage = (string)args.UserState;
// show the progress somewhere. you can interact with the GUI safely here.
}
private void worker_Completed(object sender, RunWorkerCompletedEventArgs args)
{
if (args.Error != null)
{
// handle the error
}
else if (args.Cancelled)
{
// handle the cancellation
}
else
{
// the work is finished! the result is in args.Result
}
}