在处理时更新 WPF UI - 异步等待的最佳使用
Update the WPF UI while processing - best use of async await
这是我尝试过的方法 - 它有效,就刷新 UI 而言,但我认为这不是 async / await 的最佳用途。我该如何改进?
private async void btnQuickTest_Click(object sender, RoutedEventArgs e)
{
XmlReader reader;
int rowCount = 0;
using (reader = XmlReader.Create("someXmlFile.xml"))
{
while (reader.Read())
{
rowCount++;
DoSomeProcessingOnTheUIThread(reader);
//only update UI every 100 loops
if (rowCount > 0 && rowCount % 100 == 0)
{
//yay! release the UI thread
await Task.Run(() =>
{
Application.Current.Dispatcher.Invoke(
() =>
{
txtRowCount.Text = string.Format("{0}", rowCount);
});
});
}
} //end-while
}//end-using
}
什么是更好的方法?
更新:
我根据 Clemens 的回答避免了 Dispatcher.Invoke,方法是将处理发送到后台任务,并直接在 UI 上更新进度。
我的代码现在看起来像。
private async void btnQuickTest_Click(object sender, RoutedEventArgs e)
{
XmlReader reader;
int rowCount = 0;
using (reader = XmlReader.Create("someXmlFile.xml"))
{
while (reader.Read())
{
rowCount++;
await DoSomeProcessing(reader);
//only update UI every 100 loops
if (rowCount % 100 == 0)
{
txtRowCount.Text = string.Format("{0}", rowCount);
}
} //end-while
}//end-using
MessageBox.Show("I am done!");
}
private Task DoSomeProcessing(XmlReader reader)
{
Task t =Task.Run(() =>
{
//Do my processing here.
});
return t;
}
更新#2:
反思一下,为什么我要在每个循环中创建一个新任务?
在一个后台任务中只 运行 整个循环可能会更好。并定期引发回调以显示进度;请参阅下面我的其他答案。
立即调用 Dispatcher.Invoke
的 Task 毫无意义,因为除了调度 Dispatcher 操作的一小段代码外,实际上没有任何东西在后台线程上运行。
最好直接设置文本属性,然后使用XmlReader.ReadAsync
:
private async void btnQuickTest_Click(object sender, RoutedEventArgs e)
{
using (var reader = XmlReader.Create("someXmlFile.xml"))
{
int rowCount = 0;
while (await reader.ReadAsync())
{
rowCount++;
await Task.Run(() =>
{
DoSomeWork(reader);
});
if (rowCount > 0 && rowCount % 100 == 0)
{
txtRowCount.Text = string.Format("{0}", rowCount);
}
}
}
}
您也可以考虑让您的 DoSomeWork
异步并像这样直接调用它:
await DoSomeWork(reader);
我的最终答案。
private async void btnQuickTest_Click(object sender, RoutedEventArgs e)
{
await Task.Run(() => DoSomeWorkOnBgThread( (cnt) => //HAHA! this syntax is so confusing
{
Application.Current.Dispatcher.Invoke(() =>
{
txtRowCount.Text = cnt.ToString();
}
); //end-invoke
}
));
MessageBox.Show("i am done!");
}
private void DoSomeWorkOnBgThread(Action<int> callbackFn)
{
XmlReader reader;
int rowCount = 0;
using (reader = XmlReader.Create("someXmlFile.xml"))
{
while (reader.Read())
{
rowCount++;
DoMyProcessingHere();
//only update UI every 100 loops
if (rowCount % 100 == 0)
{
callbackFn(rowCount); ///
}
} //end-while
}//end-using
}
这是我尝试过的方法 - 它有效,就刷新 UI 而言,但我认为这不是 async / await 的最佳用途。我该如何改进?
private async void btnQuickTest_Click(object sender, RoutedEventArgs e)
{
XmlReader reader;
int rowCount = 0;
using (reader = XmlReader.Create("someXmlFile.xml"))
{
while (reader.Read())
{
rowCount++;
DoSomeProcessingOnTheUIThread(reader);
//only update UI every 100 loops
if (rowCount > 0 && rowCount % 100 == 0)
{
//yay! release the UI thread
await Task.Run(() =>
{
Application.Current.Dispatcher.Invoke(
() =>
{
txtRowCount.Text = string.Format("{0}", rowCount);
});
});
}
} //end-while
}//end-using
}
什么是更好的方法?
更新: 我根据 Clemens 的回答避免了 Dispatcher.Invoke,方法是将处理发送到后台任务,并直接在 UI 上更新进度。 我的代码现在看起来像。
private async void btnQuickTest_Click(object sender, RoutedEventArgs e)
{
XmlReader reader;
int rowCount = 0;
using (reader = XmlReader.Create("someXmlFile.xml"))
{
while (reader.Read())
{
rowCount++;
await DoSomeProcessing(reader);
//only update UI every 100 loops
if (rowCount % 100 == 0)
{
txtRowCount.Text = string.Format("{0}", rowCount);
}
} //end-while
}//end-using
MessageBox.Show("I am done!");
}
private Task DoSomeProcessing(XmlReader reader)
{
Task t =Task.Run(() =>
{
//Do my processing here.
});
return t;
}
更新#2: 反思一下,为什么我要在每个循环中创建一个新任务? 在一个后台任务中只 运行 整个循环可能会更好。并定期引发回调以显示进度;请参阅下面我的其他答案。
立即调用 Dispatcher.Invoke
的 Task 毫无意义,因为除了调度 Dispatcher 操作的一小段代码外,实际上没有任何东西在后台线程上运行。
最好直接设置文本属性,然后使用XmlReader.ReadAsync
:
private async void btnQuickTest_Click(object sender, RoutedEventArgs e)
{
using (var reader = XmlReader.Create("someXmlFile.xml"))
{
int rowCount = 0;
while (await reader.ReadAsync())
{
rowCount++;
await Task.Run(() =>
{
DoSomeWork(reader);
});
if (rowCount > 0 && rowCount % 100 == 0)
{
txtRowCount.Text = string.Format("{0}", rowCount);
}
}
}
}
您也可以考虑让您的 DoSomeWork
异步并像这样直接调用它:
await DoSomeWork(reader);
我的最终答案。
private async void btnQuickTest_Click(object sender, RoutedEventArgs e)
{
await Task.Run(() => DoSomeWorkOnBgThread( (cnt) => //HAHA! this syntax is so confusing
{
Application.Current.Dispatcher.Invoke(() =>
{
txtRowCount.Text = cnt.ToString();
}
); //end-invoke
}
));
MessageBox.Show("i am done!");
}
private void DoSomeWorkOnBgThread(Action<int> callbackFn)
{
XmlReader reader;
int rowCount = 0;
using (reader = XmlReader.Create("someXmlFile.xml"))
{
while (reader.Read())
{
rowCount++;
DoMyProcessingHere();
//only update UI every 100 loops
if (rowCount % 100 == 0)
{
callbackFn(rowCount); ///
}
} //end-while
}//end-using
}