【问题标题】:C# implementing dispatcher in async/await code [duplicate]C#在异步/等待代码中实现调度程序[重复]
【发布时间】:2021-05-27 08:30:38
【问题描述】:

我有一个 wpf 应用程序,其中一个功能是复制文件。它可以正常运行,但有时会冻结 UI,因此我正在尝试向其添加 async/await 功能。

这是代码:

if (dialog.ShowDialog() == System.Windows.Forms.DialogResult.OK)
        {
            string[] files = filePickedTextBox.Text.Split('\n');
            await Task.Run(() =>
                {
                    foreach (var file in files)
                    {
                        try
                        {
                            AddValueLoadingBar(20, true);
                            File.Copy(file, Path.Combine(dialog.SelectedPath, Path.GetFileName(file)));
                            newDirTextBox.Text = Path.Combine(dialog.SelectedPath, Path.GetFileName(file));
                        }

                        catch(Exception ex)
                        {
                            newDirTextBox.Text = ex.ToString();
                        }

                        finally
                        {
                            AddValueLoadingBar(100, true);
                        }
                    }                    
                });

我尝试在多个位置添加 dispatcher.invoke,但在需要与文本框对话时,总是收到一个错误,提示它正在请求来自不同线程的请求。

我已经在这工作了几个小时,并且在网上搜索了许多 stackoverflow 线程和其他文档,但我不知道如何实现它。

我也知道我的进度条实现很糟糕......但我现在并不担心,因为我一开始就无法让它运行而不冻结。

任何帮助都会很棒。

谢谢

【问题讨论】:

标签: c# wpf async-await invoke


【解决方案1】:

这里是您使用System.Windows.Threading.Dispatcher 类的地方。

您知道对话框构造函数将在主线程上运行。所以当对话框创建时,你将主线程的调度器记录为成员变量。

然后,在 Task 线程中,确保通过此调度程序将任何更新更改调度到 Windows 线程。

using System.Windows;

class MyTestDialog
{
    readonly Dispatcher _uiThreadDispatcher = Dispatcher.CurrentDispatcher;

    public async Task DoSomethingInBackround()
    {
        await Task.Run(
             () => 
             {
                //
                // Do a bunch of stuff here
                //
                _uiThreadDispatcher.BeginInvoke(
                     new Action(
                         () => newDirTextBox.Text = "Some progress text";
                     )
                );
                // 
                // Do some more stuff
                // 
             }
        );
    }
}

【讨论】:

    【解决方案2】:

    在您的Task.Run 中,如果不调用对 UI 线程的回调,您将无法访问或更新任何 UI 元素。您的每一条语句似乎都在与 UI 交互。您需要先将所有 UI 工作提取到代码中,然后再将繁重的工作投入到任务中。

    现在,我建议使用 Microsoft 的响应式框架(又名 Rx)- NuGet System.Reactive.Windows.Threading(用于 WPF 位)并添加 using System.Reactive.Linq; - 然后您可以这样做:

    IDisposable subscription =
        files
            .Select(f => new
            {
                source = f,
                destination = Path.Combine(dialog.SelectedPath, Path.GetFileName(f))
            })
            .ToObservable()
            .SelectMany(x => Observable.Start(() =>
            {
                File.Copy(x.source, x.destination);
                return x.destination;
            }))
            .Select((d, n) => new { destination = d, progress = 100 * (n + 1) / files.Length })
            .ObserveOnDispatcher()
            .Subscribe(
                x =>
                {
                    AddValueLoadingBar(x.progress, true);
                    newDirTextBox.Text = x.destination; 
                },
                ex => newDirTextBox.Text = ex.ToString(),
                () => AddValueLoadingBar(100, true));
    

    这完成了将实际文件复制发送到后台线程所需的一切,但仍将所有更新编组到 UI。

    【讨论】:

    • .SelectMany 引入了并发,.Select(...).Merge(1) 可能更接近 OP 试图做的事情。在这种情况下,可能需要将Observable.Start 包装在Observable.Defer 中。
    • @TheodorZoulias - 这可能是一个公平的选择。
    猜你喜欢
    • 2018-11-12
    • 1970-01-01
    • 1970-01-01
    • 2021-09-04
    • 2018-09-22
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-07-07
    相关资源
    最近更新 更多