从任务工厂调度的线程更新 WPF 中的 UI 的正确方法是什么?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/16161131/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
What is the correct way to update the UI in WPF from a thread dispatched from Task Factory?
提问by Moon
I am trying to update the UI from one of my thread dispatched via Task.Factory. I have having hard time properly updating my UI.
我正在尝试从我通过Task.Factory. 我很难正确更新我的用户界面。
Here is the behavior I am observing:
这是我观察到的行为:
Task.Factory.StartNew(() =>
{
// UI does get updated from here.
}).ContinueWith(task =>
{
// UI does *not* get updated from here.
});
What is the proper way to update the UI within a thread dispatched with Task Factory?
在用 调度的线程中更新 UI 的正确方法是什么Task Factory?
Here is my actual code for your reference:
这是我的实际代码供您参考:
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);
});
回答by Gennady Vanin Геннадий Ванин
// UI does get updated from here
// UI 确实从这里更新
You should launch a new Action(() =>through DispatcherObjectin WPF
您应该在 WPF 中启动一个new Action(() =>通过DispatcherObject
Task.Factory.StartNew(() =>
? ? {
? ? ? ? // UI does get updated from here
this.Dispatcher.BeginInvoke(new Action(() =>
{
Please search for the last line in "Part 1 - Getting Started" of Alexandra Rusina's series "Parallel Programming in .NET Framework 4"
请搜索Alexandra Rusina 的系列“.NET Framework 4 中的并行编程”的“第 1 部分 - 入门”中 的最后一行
I am sure you will enjoy all sequel from this ref further on.
我相信你会更喜欢这个参考的所有续集。
Part 2- Task Cancellationdemonstrates how to use task scheduler instead:
第 2 部分 - 任务取消演示了如何改用任务调度程序:
var ui = TaskScheduler.FromCurrentSynchronizationContext();
Task.Factory.ContinueWhenAll(tasks.ToArray(),
result =>
{
var time = watch.ElapsedMilliseconds;
label1.Content += time.ToString();
}, CancellationToken.None, TaskContinuationOptions.None, ui);
instead of:
代替:
Task.Factory.ContinueWhenAll(tasks.ToArray(),
result =>
{
var time = watch.ElapsedMilliseconds;
this.Dispatcher.BeginInvoke(new Action(() =>
label1.Content += time.ToString()));
});
In response to comments
回应评论
"First of all, I am using PRISM. So, in my ViewModwl I have to use Dispatcher.Current.BeginInvoke --- I tried that. It did not help"
“首先,我使用的是 PRISM。所以,在我的 ViewModwl 中,我必须使用 Dispatcher.Current.BeginInvoke --- 我试过了。它没有帮助”
Please check the answer to "Is WPF Dispatcher the solution of multi threading problems?"related to use of dispatcher and accessing UI in Prism:
请查看“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());
}
}
"You need to make sure you are invoking on the actual UI Dispatcher, not necessarily the Current"
“您需要确保您正在调用实际的 UI 调度程序,而不一定是 Current”
You can engage the PRISM event aggregatorto ensure that you are on the UI thread or basic Dispatcher.CheckAccess Method
您可以使用PRISM 事件聚合器来确保您在 UI 线程或基本Dispatcher.CheckAccess 方法上
If you use TaskScheduler, then you should get TaskScheduler.FromCurrentSynchronizationContexton UI thread (for example, in Window.Loaded event handler, you will get on double clicking your form) and pass/share to/with tasks.
如果您使用 TaskScheduler,那么您应该进入TaskScheduler.FromCurrentSynchronizationContextUI 线程(例如,在 Window.Loaded 事件处理程序中,您将获得双击您的表单)并传递/共享给/与任务。
回答by Tormod
Look at this video. DNRTV episode with Stephen Toub. It is focused on the then upcoming .NET 4.5 features, but in the initial recap, it covers the topic of GUI marshalling from tasks using taskschedulers nicely.
看看这个视频。DNRTV 与斯蒂芬·图布的剧集。它专注于当时即将推出的 .NET 4.5 功能,但在最初的回顾中,它很好地涵盖了使用任务调度程序从任务中编组 GUI 的主题。
回答by Panagiotis Kanavos
The easiest way to update the UI is through the SynchronizationContext.Post/Sendmethods. SynchronizationContext hides the underlying UI library (WPF or WinForms) and ensures that the action you specify executes on the proper thread.
更新 UI 的最简单方法是通过SynchronizationContext.Post/ Send方法。SynchronizationContext 隐藏了底层 UI 库(WPF 或 WinForms)并确保您指定的操作在正确的线程上执行。
Send blocks until the UI finishes processing the action while Post executes the action asynchronously.
Send 阻塞直到 UI 完成处理动作,而 Post 异步执行动作。
To update your UI in an asynchronous manner you can use code like this:
要以异步方式更新您的 UI,您可以使用如下代码:
SyncrhonizationContext.Current.Post(value=>myTextBox.Text=(string)value,"23455");
A similar option is to specify TaskScheduler.FromCurrentSynchronizationContext() in ContinueWith to have your continuation execute in the UI thread:
一个类似的选项是在 ContinueWith 中指定 TaskScheduler.FromCurrentSynchronizationContext() 以使您的延续在 UI 线程中执行:
.ContinueWith(t=>{
myTextBox.Text="Some Value";
});
This is equivalent to calling SynchronizationContext.Current.Send
这相当于调用 SynchronizationContext.Current.Send
You also mention that you use MVVM and PRISM and that the asynchronous operation executes in the ViewModel. Changes to ViewModel properties will not appear in the view unless the properties or your asynchronous code also raises the NotifyPropertyChanged event. This is a PRISM issue though, not a TPL issue.
您还提到您使用了 MVVM 和 PRISM,并且异步操作在 ViewModel 中执行。对 ViewModel 属性的更改不会出现在视图中,除非属性或您的异步代码也引发 NotifyPropertyChanged 事件。不过,这是 PRISM 问题,而不是 TPL 问题。
I also noticed that you publish property changed events instead of raising the NotifyPropertyChanged event. Data Binding depends on receiving NotifyPropertyChanged from the source properties. Unless you add code to somehow raise the proper events, the view controls will not be updated.
我还注意到您发布了属性更改事件而不是引发 NotifyPropertyChanged 事件。数据绑定取决于从源属性接收 NotifyPropertyChanged。除非您添加代码以某种方式引发正确的事件,否则视图控件将不会更新。
ViewModels in PRISM typically inherit from NotificationObjectwhich implements the INotifyPropertyChanged interface. Try calling the RaisePropertyChanged method to raise the NotifyPropertyChanged event inside your proeprties and see whether this solves your problem.
PRISM 中的 ViewModels 通常从实现 INotifyPropertyChanged 接口的NotificationObject继承。尝试调用 RaisePropertyChanged 方法在您的属性中引发 NotifyPropertyChanged 事件,看看这是否能解决您的问题。

