1

我正在尝试从通过Task.Factory. 我很难正确更新我的 UI。

这是我观察到的行为:

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

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

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

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);
    });
4

3 回答 3

3

// 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 --- 我试过了。它没有帮助”

请查看“WPF Dispatcher 是多线程问题的解决方案吗?”的答案。与在 Prism 中使用调度程序和访问 UI 相关:

// 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,而不一定是 Current

您可以使用PRISM 事件聚合器以确保您在 UI 线程或基本Dispatcher.CheckAccess 方法上

如果您使用 TaskScheduler,那么您应该进入TaskScheduler.FromCurrentSynchronizationContextUI 线程(例如,在 Window.Loaded 事件处理程序中,您将继续双击您的表单)并传递/共享到/与任务。

于 2013-04-23T06:49:44.077 回答
0

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

发送块,直到 UI 完成对操作的处理,而 Post 异步执行操作。

要以异步方式更新 UI,您可以使用如下代码:

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

一个类似的选项是在 ContinueWith 中指定 TaskScheduler.FromCurrentSynchronizationContext() 以让您的延续在 UI 线程中执行:

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

这相当于调用 SynchronizationContext.Current.Send

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

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

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

于 2013-04-23T13:33:31.203 回答
0

看看这个视频。斯蒂芬·图布的 DNRTV 剧集。它专注于随后即将推出的 .NET 4.5 功能,但在最初的回顾中,它很好地涵盖了使用任务调度程序从任务中编组 GUI 的主题。

于 2013-04-23T04:57:43.663 回答