C#在异步/等待代码中实现dispatcher

C#在异步/等待代码中实现dispatcher,c#,wpf,async-await,invoke,C#,Wpf,Async Await,Invoke,我有一个wpf应用程序,其中一个功能是复制文件。它功能正常,但有时会冻结UI,因此我尝试向其添加异步/等待函数 代码如下: if (dialog.ShowDialog() == System.Windows.Forms.DialogResult.OK) { string[] files = filePickedTextBox.Text.Split('\n'); await Task.Run(() =>

我有一个wpf应用程序,其中一个功能是复制文件。它功能正常,但有时会冻结UI,因此我尝试向其添加异步/等待函数

代码如下:

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线程和其他文档,但我不知道如何实现这一点

我也知道我的进度条实现很糟糕,但我现在并不担心,因为我无法在没有冻结的情况下运行它

任何帮助都会很好

Thx

这里是您使用该类的地方

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

然后,在任务线程内,确保通过该调度程序将任何更新更改调度到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
                // 
             }
        );
    }
}

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

现在,我建议使用微软的反应式框架(aka Rx)-NuGet
System.Reactive.Windows.Threading
(对于WPF位),并使用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。

这不是winform,因此不使用backgroundworker。编辑主要帖子以陈述其WPF。这是否回答了您的问题。您应该使用
BeginInvoke
而不是
Invoke
,因为后者可能导致线程deadlock@MattU-
BackgroundWorker
不再是做这种事情的方式。现在有很多更好的选项。@user15919119-为什么要在WPF中使用
System.Windows.Forms
对话框?我会更改它。谢谢!与引入并发性的
.SelectMany
不同,
.Select(…).Merge(1)
可能更接近OP试图做的事情。在这种情况下,可能需要将
Observable.Start
包装成
Observable.Defer
。@TheodorZoulias-这可能是一个合理的选择。