从任务工厂调度的线程更新WPF中的UI的正确方法是什么?

本文关键字:UI 方法 是什么 中的 WPF 工厂 任务 调度 线程 更新 | 更新日期: 2023-09-27 18:10:21

我正试图从通过Task.Factory分派的线程之一更新UI。我很难正确地更新我的UI。

下面是我观察到的行为:

Task.Factory.StartNew(() =>
    {
        // UI does get updated from here.
    }).ContinueWith(task =>
        {
            // UI does *not* get updated from here.
        });

Task Factory调度的线程中更新UI的正确方法是什么?

这是我的实际代码供您参考:

private string CurrentProcess
{
    set { _eventAggregator.GetEvent<CurrentProcessUpdatedEvent>().Publish(value); }
}
private double ProgressPercentage
{
    set
    {
        _eventAggregator.GetEvent<ProgressPercentageUpdatedEvent>()
                        .Publish(Utilities.GetProgressPercentage(value));
    }
}

TaskScheduler uiScheduler = TaskScheduler.FromCurrentSynchronizationContext();
var tasks = new List<Task<DataTable>>();
string siteCollectionUrl;
string connectionString;
try
{
    Dictionary<string, object> session = ApplicationContext.Current.Session;
    try
    {
        if ((double) session["ProgressPercentage"] > 0) return;
    }
    catch
    {
    }
    siteCollectionUrl = (string) session["SiteCollection"];
    connectionString = (string) session["Database"];
}
catch
{
    return;
}
_eventAggregator.GetEvent<IsProcessingChangedEvent>().Publish(true);
CurrentProcess = "Loading resources.";
Task<DataTable> spTask = Task<DataTable>.Factory.StartNew(() =>
    {
        using (ChannelFactory<ISharePointService> service = Utilities.GetSharePointService())
        {
            ISharePointService sharePointService = service.CreateChannel();
            DataTable spDatatable = sharePointService.GetResources(siteCollectionUrl);
            Task.Factory.StartNew(() => { ProgressPercentage = 10; }, CancellationToken.None, TaskCreationOptions.None, uiScheduler);
            return spDatatable;
        }
    });
tasks.Add(spTask);
Task<DataTable> buildTableTask = Task<DataTable>.Factory.ContinueWhenAll(tasks.ToArray(), t =>
    {
        DataTable spDatatable = t[0].Result;
        double percent = 10/spDatatable.Rows.Count;
        var columnMap = new Dictionary<string, string>
            {
                {"IsValid", null},
                {"Reason", null},
                {"SPID", "ID"},
                {"DBID", "EXTID"},
                {"Name", "Title"},
                {"Account", "SharePointAccount"},
                {"Email", "Email"},
                {"Generic", "Generic"},
                {"Department", "Department"},
                {"TempDept", "TempDept"},
                {"Role", "Role"},
                {"TempRole", "TempRole"},
                {"HolidaySchedule", "HolidaySchedule"},
                {"WorkHours", "WorkHours"}
            };
        DataTable spResources = BuildDataTable(columnMap);
        foreach (DataRow dataRow in spDatatable.Rows)
        {
            DataRow row = spResources.NewRow();
            foreach (var pair in columnMap)
            {
                try
                {
                    row[pair.Key] = dataRow[pair.Value];
                }
                catch
                {
                }
            }
            spResources.Rows.Add(row);
            Task.Factory.StartNew(() => { ProgressPercentage = percent; }, CancellationToken.None, TaskCreationOptions.None, uiScheduler);
        }
        return spResources;
    });
tasks.Add(buildTableTask);
Task<DataTable> dbTask = Task<DataTable>.Factory.StartNew(() =>
    {
        using (var sqlConnection = new SqlConnection(connectionString))
        {
            using (var sqlCommand = new SqlCommand(SQL, sqlConnection))
            {
                sqlConnection.Open();
                using (SqlDataReader sqlDataReader = sqlCommand.ExecuteReader())
                {
                    var dataTable = new DataTable();
                    dataTable.Load(sqlDataReader);
                    Task.Factory.StartNew(() => { ProgressPercentage = 10; }, CancellationToken.None, TaskCreationOptions.None, uiScheduler);
                    return dataTable;
                }
            }
        }
    });
tasks.Add(dbTask);
Task.Factory.ContinueWhenAll(tasks.ToArray(), t =>
    {
        DatabaseResources = t[2].Result;
        DataTable sharePointResources = t[1].Result;
        if (sharePointResources != null)
        {
            int resourceIndex = 1;
            int totalResources = sharePointResources.Rows.Count;
            double percentPoint = 70/totalResources;
            foreach (DataRow row in sharePointResources.Rows)
            {
                DataRow currentRow = row;
                Task.Factory.StartNew(() =>
                    {
                        CurrentProcess = string.Format("[{0}/{1}] Processing: {2}",
                                                        resourceIndex++, totalResources,
                                                        currentRow["Name"]);
                    }, CancellationToken.None, TaskCreationOptions.None, uiScheduler);
                bool isValid = true;
                var reasons = new List<string>();
                DataRow[] dataRows =
                    _databaseResources.Select(string.Format("ResourceID = {0}", row["DBID"]));
                if (dataRows.Any())
                {
                    DataRow dataRow = dataRows[0];
                    string tempDept = (row["TempDept"] ?? string.Empty).ToString();
                    string dept = (row["Department"] ?? string.Empty).ToString();
                    string tempRole = (row["TempRole"] ?? string.Empty).ToString();
                    string role = (row["Role"] ?? string.Empty).ToString();
                    string hs = (row["HolidaySchedule"] ?? string.Empty).ToString();
                    string dbhs = (dataRow["HolidaySchedule"] ?? string.Empty).ToString();
                    string wh = (row["WorkHours"] ?? string.Empty).ToString();
                    string dbwh = (dataRow["WorkHours"] ?? string.Empty).ToString();
                    if (string.IsNullOrEmpty(dept))
                    {
                        if (!dept.Equals(tempDept))
                        {
                            isValid = false;
                            reasons.Add("Department does not match Temp Dept");
                        }
                    }
                    if (string.IsNullOrEmpty(role))
                    {
                        if (!role.Equals(tempRole))
                        {
                            isValid = false;
                            reasons.Add("Role does not match Temp Role");
                        }
                    }
                    if (string.IsNullOrEmpty(hs))
                    {
                        if (!hs.Equals(dbhs))
                        {
                            isValid = false;
                            reasons.Add("Holiday Schedule does not match Holiday Schedule from database");
                        }
                    }
                    if (string.IsNullOrEmpty(wh))
                    {
                        if (!wh.Equals(dbwh))
                        {
                            isValid = false;
                            reasons.Add("Work Hours does not match Work Hours from database");
                        }
                    }
                }
                else
                {
                    isValid = false;
                    reasons.Add("Resource does not exist in database");
                }
                row["IsValid"] = isValid;
                row["Reason"] = string.Join("'n", reasons.ToArray());
                Task.Factory.StartNew(() => { ProgressPercentage = percentPoint; }, CancellationToken.None, TaskCreationOptions.None, uiScheduler);
            }
            SharePointResources = sharePointResources;
        }
        _eventAggregator.GetEvent<ProgressPercentageUpdatedEvent>()
                        .Publish(Utilities.ResetProgressPercentage());
        _eventAggregator.GetEvent<IsProcessingChangedEvent>().Publish(false);
    });

从任务工厂调度的线程更新WPF中的UI的正确方法是什么?

//UI从这里得到更新

您应该在WPF中启动new Action(() =>DispatcherObject

Task.Factory.StartNew(() =>
    {
        // UI does get updated from here
        this.Dispatcher.BeginInvoke(new Action(() => 
        {

请搜索Alexandra Rusina系列"。net Framework 4中的并行编程"的"第1部分-入门"中的最后一行

我相信你会喜欢所有的续集从这个参考进一步。

第2部分-任务取消演示了如何使用任务调度程序:

var ui = TaskScheduler.FromCurrentSynchronizationContext();
Task.Factory.ContinueWhenAll(tasks.ToArray(),
    result =>
    {
        var time = watch.ElapsedMilliseconds;
        label1.Content += time.ToString();
    }, CancellationToken.None, TaskContinuationOptions.None, ui);

代替:

Task.Factory.ContinueWhenAll(tasks.ToArray(),
      result =>
      {
          var time = watch.ElapsedMilliseconds;
          this.Dispatcher.BeginInvoke(new Action(() =>
              label1.Content += time.ToString()));
      });  

回复评论

"首先,我使用的是PRISM。在ViewModwl中,我需要使用dispatcher。current。begininvoke——我试过了。它没有帮助"

请查看有关在Prism中使用Dispatcher和访问UI的"WPF Dispatcher是多线程问题的解决方案吗?"的答案:

// Not a UI component
public class MyDomainService : IMyDomainService
{
   private readonly IDispatcher _dispatcher;
   public MyDomainService(IDispatcher dispatcher) 
   {
      _dispatcher = dispatcher;
   }
   private void GotResultFromBackgroundThread()
   {
       _dispatcher.Dispatch(() => DoStuffOnForegroundThread());
   }
}

"你需要确保你正在调用实际的UI Dispatcher,而不一定是当前的"

您可以使用PRISM事件聚合器来确保您位于UI线程或基本Dispatcher上。检查访问方法

如果你使用TaskScheduler,那么你应该在UI线程上获得TaskScheduler.FromCurrentSynchronizationContext(例如,在windows中)。加载事件处理程序,您将双击表单)并将/share传递给/with任务。

看这个视频。与Stephen Toub的DNRTV节目。它的重点是即将到来的。net 4.5特性,但在最初的回顾中,它很好地涵盖了使用任务调度器从任务中进行GUI编组的主题。

更新UI最简单的方法是通过SynchronizationContext。Post/发送方法。SynchronizationContext隐藏底层的UI库(WPF或WinForms),并确保您指定的操作在正确的线程上执行。

Send阻塞,直到UI处理完动作,而Post异步执行动作。

以异步方式更新你的UI,你可以使用这样的代码:

SyncrhonizationContext.Current.Post(value=>myTextBox.Text=(string)value,"23455");

一个类似的选择是在ContinueWith中指定TaskScheduler.FromCurrentSynchronizationContext(),让你的continuation在UI线程中执行:

.ContinueWith(t=>{
    myTextBox.Text="Some Value";
 });

这相当于调用SynchronizationContext.Current.Send

您还提到您使用MVVM和PRISM,并且异步操作在ViewModel中执行。对ViewModel属性的更改将不会出现在视图中,除非属性或您的异步代码也引发NotifyPropertyChanged事件。这是PRISM的问题,而不是TPL的问题。

我还注意到你发布属性更改事件,而不是引发NotifyPropertyChanged事件。数据绑定依赖于从源属性接收NotifyPropertyChanged。除非您添加代码以某种方式引发适当的事件,否则视图控件将不会更新。

PRISM中的ViewModels通常继承自NotificationObject,它实现了INotifyPropertyChanged接口。尝试调用RaisePropertyChanged方法来引发属性内的NotifyPropertyChanged事件,看看这是否解决了您的问题。