ICommand Execute()Task.ContinueWith()

时间:2016-03-29 11:56:45

标签: c# wpf multithreading task

在WPF应用程序中实现Execute()的类的ICommand中,我对一个返回Task的方法进行外部调用

ICommand类:

public static void Execute(object parameter)
{
    Cancel(arg1, arg2);
}

private static void Cancel(IList<object> arg1, object arg2)
{

    Task<object> cancelTask = service.AmendAsync
    (
        CancelTokenSource.Token, 
        object arg2
    );

    ProcessCancellingResponse(arg1, arg2);
}

private static void ProcessCancellingResponse(IList<object> arg1, Task<object> cancelTask)
{
    cancelTask.ContinueWith
    (
        task =>
        {
            Update(task.Result.Response);
        },
        CancelTokenSource.Token,
        TaskContinuationOptions.AttachedToParent | TaskContinuationOptions.OnlyOnRanToCompletion,
        TaskScheduler.FromCurrentSynchronizationContext()
    );
}

服务类:

public Task<object> AmendAsync(CancellationToken cancellationToken, object arg1)
{
    return Task<object>.Factory.StartNew
    (
        () =>
        {
            ...
        },
        cancellationToken,
        TaskCreationOptions.None,
        TaskScheduler.Default
    );
}

我的问题是

  1. 什么线程调用ICommend Execute()它是UI线程吗?
  2. cancelTask.ContinueWith()等待UI线程或后台线程吗?即如果Task需要很长时间并且在UI线程上等待,则UI可能会冻结。

3 个答案:

答案 0 :(得分:2)

根据Domysee评论,我会在这里清楚,Execute 总是在UI线程中运行,但你可以在回调中做任何你想做的事情,包括运行背景螺纹,

关于延续,如果你没有明确告诉他线程继续,它将在TaskScheduler.Current上完成它的工作,否则它将继续你定义的调度程序。

无论如何,考虑使用async\await和\ out捕获来进行延续

await Task.Run(() => ).ConfigureAwait(true);

await Task.Run(() => ).ConfigureAwait(false);

<强>更新

根据问题更新,

执行 - &gt; UI线程

取消=&gt; UI线程

AmendAsync =&gt;后台线程

ContinueWith =&gt; UI线程(因为你写了FromCurrentSynchronizationContext

答案 1 :(得分:2)

  

什么线程调用ICommend Execute()它是UI线程吗?

是的,它始终位于UI线程上。

  

cancelTask​​.ContinueWith()会在UI线程上还是在后台线程上等待?

ContinueWith只是常规方法调用。没有魔力。所以打破它:

此:

private static void ProcessCancellingResponse(IList<object> arg1, Task<object> cancelTask)
{
  cancelTask.ContinueWith
  (
    task =>
    {
        Update(task.Result.Response);
    },
    CancelTokenSource.Token,
    TaskContinuationOptions.AttachedToParent | TaskContinuationOptions.OnlyOnRanToCompletion,
    TaskScheduler.FromCurrentSynchronizationContext()
  );
}

与此相同:

private static void ProcessCancellingResponse(IList<object> arg1, Task<object> cancelTask)
{
  Action<Task> continuation = task => { Update(Task.Result.Response); };
  var token = CancelTokenSource.Token;
  var options = TaskContinuationOptions.AttachedToParent | TaskContinuationOptions.OnlyOnRanToCompletion;
  var scheduler = TaskScheduler.FromCurrentSynchronizationContext();
  cancelTask.ContinueWith(continuation, token, options, scheduler);
}

由于在UI线程上调用ProcessCancellingResponse,因此scheduler将是在该UI线程上执行其任务的调度程序。因此,continuation将在该UI线程上运行。

另一方面,我看到至少有一个错误:AttachedToParent几乎肯定是错的。承诺任务(异步任务)几乎不应该附加任务。

实施也可以更清洁:

private static async Task ProcessCancellingResponseAsync(IList<object> arg1, Task<object> cancelTask)
{
  var result = await cancelTask;
  Update(result.Response);
}

public object Amend(CancellationToken cancellationToken, object arg1)
{
  ...
}

private static void Cancel(IList<object> arg1, object arg2)
{
  Task<object> cancelTask = Task.Run(() => service.Amend
  (
    CancelTokenSource.Token, 
    object arg2
  );

  ProcessCancellingResponse(arg1, arg2);
}

答案 2 :(得分:1)

  1. 直接从UI调用时,ICommand Execute()在主线程(UI线程)上运行。

  2. 这取决于代码的位置。如果它直接位于Execute中,它也将在主线程上运行,因为您将调度程序指定为TaskScheduler.FromCurrentSynchronizationContext()