在处理时更新 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
}