我有一个带有多个Dispatcher
(也称为GUI线程,又称消息泵)的应用程序,以确保GUI的缓慢,无响应部分运行而不会过多地影响应用程序的其余部分。我也经常使用Task
。
目前我的代码有条件地在Action
或TaskScheduler
上运行Dispatcher
,然后直接或通过使用{手动创建一个Task
返回TaskCompletionSource
{1}}。然而,这种分裂的个性设计使得处理取消,异常等等都比我想要的复杂得多。我想在任何地方使用Task
,DispatcherOperation
无处可去。要做到这一点,我需要在调度员上安排任务 - 但是如何?
如何为任何给定的TaskScheduler
获得Dispatcher
?
编辑:经过以下讨论,我决定采用以下措施:
public static Task<TaskScheduler> GetScheduler(Dispatcher d) {
var schedulerResult = new TaskCompletionSource<TaskScheduler>();
d.BeginInvoke(() =>
schedulerResult.SetResult(
TaskScheduler.FromCurrentSynchronizationContext()));
return schedulerResult.Task;
}
答案 0 :(得分:14)
第1步:创建扩展方法:
public static Task<TaskScheduler> ToTaskSchedulerAsync (
this Dispatcher dispatcher,
DispatcherPriority priority = DispatcherPriority.Normal) {
var taskCompletionSource = new TaskCompletionSource<TaskScheduler> ();
var invocation = dispatcher.BeginInvoke (new Action (() =>
taskCompletionSource.SetResult (
TaskScheduler.FromCurrentSynchronizationContext ())), priority);
invocation.Aborted += (s, e) =>
taskCompletionSource.SetCanceled ();
return taskCompletionSource.Task;
}
第2步:使用扩展方法:
旧语法:
var taskSchedulerAsync = Dispatcher.CurrentDispatcher.ToTaskSchedulerAsync ();
var taskFactoryAsync = taskSchedulerAsync.ContinueWith<TaskFactory> (_ =>
new TaskFactory (taskSchedulerAsync.Result), TaskContinuationOptions.OnlyOnRanToCompletion);
// this is the only blocking statement, not needed once we have await
var taskFactory = taskFactoryAsync.Result;
var task = taskFactory.StartNew (() => { ... });
新语法:
var taskScheduler = await Dispatcher.CurrentDispatcher.ToTaskSchedulerAsync ();
var taskFactory = new TaskFactory (taskScheduler);
var task = taskFactory.StartNew (() => { ... });
答案 1 :(得分:9)
不幸的是,没有内置的方法来做到这一点。没有专门用于在Dispatcher
中包裹TaskScheduler
的内置类 - 我们最接近的是包裹SynchronizationContext
的内容。从TaskScheduler
构建SynchronizationContext
的唯一公开API是Paul Michalik提到的TaskScheduler.FromCurrentSynchronizationContext
- 并且正如您所观察到的那样,只有在您已经处于相关同步状态时才有效上下文(即,在相关调度员的主题上)。
所以你有三个选择:
TaskScheduler.FromCurrentSynchronizationContext
。Dispatcher.BeginInvoke
在调度程序线程上运行一些代码,并在该代码中调用TaskScheduler.FromCurrentSynchronizationContext
。 (换句话说,如果你不能安排1.自然发生,强迫它发生。)答案 2 :(得分:4)
看看TaskScheduler.FromCurrentSynchronizationContext
。即使应用程序强加了特定的线程模型,Task框架也提供了一种非常灵活的方式来配置计算绑定操作的执行。
编辑:
嗯,很难从你发布的内容中得到更明确的信息。我知道你正在运行一种多视图应用程序,每个视图都有独立的调度程序,对吗?由于所有调度归结为提取SynchronizationContext
和Post
- 您可以在某处获取正确的TaskScheduler
(具有正确的SynchronizationContext
的那个)你的观点有一个。一种简单的方法是在配置过程中获取TaskScheduler:
// somewhere on GUI thread you wish to invoke
// a long running operation which returns an Int32 and posts
// its result in a control accessible via this.Text
(new Task<Int32>(DoSomeAsyncOperationReturningInt32)
.ContinueWith(tTask => this.Text = tTask.Result.ToString(),
TaskScheduler.FromCurrentSynchronizationContext)).Start();
不确定这是否有帮助,如果您正在广泛使用任务,那么您可能已经知道......
答案 3 :(得分:3)
您可以将整个功能写在一行:
public static Task<TaskScheduler> ToTaskSchedulerAsync(this Dispatcher dispatcher,
DispatcherPriority priority = DispatcherPriority.Normal)
{
return dispatcher.InvokeAsync<TaskScheduler>(() =>
TaskScheduler.FromCurrentSynchronizationContext(), priority).Task;
}
以及那些对默认UI线程感兴趣的人可能会发现以下内容足以获得:
var ts = Application.Current.Dispatcher.Invoke<TaskScheduler>(() => TaskScheduler.FromCurrentSynchronizationContext());
答案 4 :(得分:0)
这就是我总是将异步函数调用转换为同步函数调用(对互联网上某人的称赞):
public static class ThreadingUtils
{
public static TaskScheduler GetScheduler(Dispatcher dispatcher)
{
using (var waiter = new ManualResetEvent(false))
{
TaskScheduler scheduler = null;
dispatcher.BeginInvoke(new Action(() =>
{
scheduler =
TaskScheduler.FromCurrentSynchronizationContext();
waiter.Set();
}));
waiter.WaitOne();
return scheduler;
}
}
}
变异:
if (!waiter.WaitOne(2000))
{
//Timeout connecting to server, log and exit
}