了解为什么TPL Task可以在没有FromCurrentSynchronizationContext的情况下更新UI

3

我正在使用VS2012、WPF和MVVM进行TPL。我有一个问题,我认为我知道答案,但想确定一下。请考虑以下代码片段:

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中声明,并通过适当的通知让View知道它们何时被更改。例如:
public string StatusData
{
    get { return _StatusData; }
    set { NotifySetProperty(ref _StatusData, value, () => StatusData); }
}
private string _StatusData;

这是我的问题 - 目前,这个代码能够正常工作。但是我并没有在ContinueWith中声明Task要通过UI线程(FromCurrentSynchronizationContext)运行或更新。
是否因为MVVM模式?换句话说,被更新的属性是VM本地的,并且它们有通知机制用于更新视图,由于通过绑定实现了解耦,所以才起作用?还是仅仅因为特殊情况而侥幸成功,我应该费劲地声明一个ContinueWith来在UI线程上更新进度呢?
1个回答

2

UI相关的内容只能从UI线程更新,而任何绑定到UI的CLR属性都可以从后台线程更新,它们没有线程关联问题。

就像您在示例中发布的那样,您只能从后台线程更新View Model属性,这是完全可以的,但是如果您尝试直接更新进度条文本,它将失败,因为progressBar是UI组件,只能从UI线程更新。


假设您有一个绑定到ViewModel类中Name属性的TextBlock

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

如果您尝试直接更新文本,您将遇到著名的线程亲和性问题:

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

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

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

谢谢Rohit。看起来这一切能够工作的关键是Bindings。按设计它是一个松散的连接,那么这是否意味着它们在技术上运行在不同的线程上,并通过某种魔法避免了整个跨线程问题? - Ernie S
1
绑定刷新的关键是属性更改事件,我们使用INPC来实现。绑定引擎在内部侦听该事件,并且可能会在UI调度程序上更新UI对象,从而避免任何跨线程问题。 - Rohit Vats

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接