我正在 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 中的 UI 线程(FromCurrentSynchronizationContext)声明要运行或更新的任务。

是因为 MVVM 模式吗?换句话说,因为正在更新的属性对于 VM 来说是本地的,并且因为它们有更新 View 的通知,并且因为通过绑定(bind)失去耦合,所以它的工作原理?或者我只是因为情况而幸运,我应该通过声明 ContinueWith 来更新 UI 线程上的进度的麻烦?

最佳答案

UI related stuff can only be updated from UI thread 虽然绑定(bind)到 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 绑定(bind)到 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");

关于c# - 理解为什么 TPL 任务可以在没有 FromCurrentSynchronizationContext 的情况下更新 UI,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/20765085/

10-14 18:34