从任务工厂调度的线程更新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);
});
//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事件,看看这是否解决了您的问题。