【问题标题】:Understanding why TPL Task can update UI withOUT FromCurrentSynchronizationContext了解为什么 TPL 任务可以在没有 FromCurrentSynchronizationContext 的情况下更新 UI
【发布时间】:2013-12-24 18:06:32
【问题描述】:

我在 VS2012,WPF 和 MVVM 中做一些 TPL。我有一个问题,我想我知道答案,但我想确定。考虑一下这个 sn-p:

TaskCanceller = new CancellationTokenSource();
TaskLoader = Task<object>.Factory.StartNew(() =>
{
    //Test the current query
    DataRepository dr = new DataRepository(DataMappingSelected);
    string test = dr.TestMappingConnection();
    if (test.IsNotNullEmpty())
        throw new DataConnectionException(test);

    //Create the CSV File
    DataQueryCsvFile CsvFile = new DataQueryCsvFile();
    CsvFile.FileName = IO.Path.GetFileName(FilePath);
    CsvFile.FilePath = IO.Path.GetDirectoryName(FilePath);

    CsvFile.DataMapping = DataMappingSelected;
    CsvFile.DataBrowserQuery = DataQueryHolder;

    //Allow for updates to the UI
    CsvFile.PageExportComplete += (s, e) =>
    {
        if (TaskCanceller.IsCancellationRequested)
            (s as DataQueryCsvFile).IsSaveCancellationRequested = true;

        StatusData = String.Format("{0} of {1} Complete", e.ProgressCount, e.TotalCount);
        StatusProgress = (100 * e.ProgressCount / e.TotalCount);
    };

    CsvFile.SaveFile();

    return CsvFile;
});

我有一个 DataQueryCsvFile 类。它的目的是根据一组传递的查询参数创建一个 CSV 文本文件,其结果可能非常大。因此导出“分页”查询生成的表,因此它不会破坏用户的内存。它的成员中有一个名为 PageExportComplete 的事件,每当将“页面”写入文件时都会调用该事件 - 例如一次 1000 条记录。下面的代码使用此事件来更新 UI 上的进度指示器。

进度指示器(StatusData 和 StatusProgress)在 VM 中声明,并带有适当的通知,以便让视图知道它们何时发生更改。例如:

public string StatusData
{
    get { return _StatusData; }
    set { NotifySetProperty(ref _StatusData, value, () => StatusData); }
}
private string _StatusData;

这是我的问题 - 按原样,这非常有效。但是为什么因为我没有在 ContinueWith 中声明要通过 UI 线程 (FromCurrentSynchronizationContext) 运行或更新的任务。

是因为 MVVM 模式吗?换句话说,因为正在更新的属性是虚拟机本地的,并且因为它们具有更新视图的通知,并且由于通过绑定而失去耦合,所以它的工作原理?还是我只是运气好,我应该通过声明 ContinueWith 来更新 UI 线程的进度?

【问题讨论】:

    标签: c# wpf multithreading events mvvm


    【解决方案1】:

    UI related stuff can only be updated from UI thread 虽然任何绑定到 UI 的 CLR 属性都可以从后台线程更新,但它们没有线程关联问题。

    就像您在示例中发布的那样,you are only updating View model properties from background thread which is perfectly fine 但如果您是 try updating Progress bar text directly, it will fall miserably,因为 progressBar 是 UI 组件,只能从 UI 线程更新。


    假设您已将 TextBlock 绑定到 ViewModel 类中的 Name 属性:

    <TextBlock x:Name="txt" Text="{Binding Name}"/>
    

    如果你尝试直接更新文本,你会遇到著名的线程关联问题:

    Task.Factory.StartNew(() => txt.Text = "From background");
    

    但如果您尝试更新 ViewModel Name 属性,它会正常工作,因为这里没有从后台线程访问 UI 内容:

    ViewModelClass vm = DataContext as ViewModelClass;
    Task.Factory.StartNew(() => vm.Name = "From background");
    

    【讨论】:

    • 谢谢罗希特。所以看起来这一切的关键实际上是绑定。按照设计,这是一个丢失的连接,所以这是否意味着它们在技术上运行在单独的线程上,并且通过某种魔法你可以避免整个跨线程问题?
    • 绑定刷新的关键是我们使用INPC实现的属性更改事件。绑定引擎在内部侦听该事件,并可能在 UI 调度程序上编组更新 UI 对象。因此避免了任何跨线程问题。
    猜你喜欢
    • 1970-01-01
    • 2013-12-10
    • 2015-02-08
    • 1970-01-01
    • 1970-01-01
    • 2022-01-14
    • 2015-06-15
    • 2015-11-02
    • 1970-01-01
    相关资源
    最近更新 更多