ContinueWith丢失SynchronizationContext

3
在下面的代码片段中,SynchronizationContext被丢失了,因此也丢失了CurrentCultureCurrentUICultureLog()来自于这个答案
public async Task<ActionResult> Index()
{
    Log("before GetAsync");
    await new HttpClient().GetAsync("http://www.example.com/")
        .ContinueWith(request =>
        {
            Log("ContinueWith");
            request.Result.EnsureSuccessStatusCode();
        }, TaskContinuationOptions.AttachedToParent);

    return View();
}

static void Log(string message)
{
    var ctx = System.Threading.SynchronizationContext.Current;
    System.Diagnostics.Debug.Print("{0}; thread: {1}, context: {2}, culture: {3}, uiculture: {4}",
        message,
        System.Threading.Thread.CurrentThread.ManagedThreadId,
        ctx != null ? ctx.GetType().Name : String.Empty,
        System.Threading.Thread.CurrentThread.CurrentCulture.Name,
        System.Threading.Thread.CurrentThread.CurrentUICulture.Name);
}

以下是输出内容:

在执行 GetAsync 之前,线程:56,上下文:AspNetSynchronizationContext,区域设置:nl,UI 区域设置:nl
ContinueWith;线程:46,上下文:,区域设置:nl-BE,UI 区域设置:en-US

在执行 GetAsync 之前,区域设置和 UI 区域设置的值是我在 Application_BeginRequest 中设定的。但是在 ContinueWith 中,上下文丢失了,区域设置被设置为浏览器提供的值,UI 区域设置被设置为默认值。

按照我的理解,所有涉及到 AspNetSynchronizationContext 的操作都应该自动完成。我的代码有什么问题呢?


你确定这里需要使用 ContinueWithTaskContinuationOptions.AttachedToParent 吗?请查看 这里,以确保 TaskContinuationOptions.AttachedToParent 可以给你想要的行为。 - noseratio - open to work
@Noseratio 我相信是这样的。请参见为什么这个异步代码有时会失败,而且只有在没有观察到时才会失败?。如果我的解决方案不正确,请随意发布更好的解决方案。 - user247702
@Noseratio,完全避免使用ContinueWith是否更好?这让我感觉自己毫无理由地陷入了困境。简而言之,我正在进行Web API请求,反序列化响应并执行回调。 - user247702
1
是的,我认为在这里您真的不需要 ContinueWith。为什么不能只使用 async/await 呢? - noseratio - open to work
1
这个项目是我们第一次使用async,我猜在我们的尝试中,我们发现ContinueWith(表面上)可行,所以我们最终保留了那段代码。我会尝试重写调用,感谢您的建议。 - user247702
没问题,我也试着回答了第二条评论这里 - noseratio - open to work
2个回答

6
为了在请求上下文线程中强制调度继续执行,您需要指定在调度继续执行时应使用的 TaskScheduler
public async Task<ActionResult> Index()
{
    Log("before GetAsync");
    await new HttpClient().GetAsync("http://www.example.com/")
        .ContinueWith(request =>
        {
            Log("ContinueWith");
            request.Result.EnsureSuccessStatusCode();
        }, 
        TaskContinuationOptions.AttachedToParent,
        CancellationToken.None,
        TaskScheduler.FromCurrentSynchronizationContext());

    return View();
}

然而,您正在使用await,它会自动将继续操作移到当前的SynchronizationContext中。您应该能够执行以下操作:

public async Task<ActionResult> Index()
    {
        Log("before GetAsync");
        HttpResponseMessage request = await new HttpClient().GetAsync("http://www.example.com/");

        //everything below here is you 'continuation' on the request context
        Log("ContinueWith");
        request.EnsureSuccessStatusCode();

        return View();
    }

第一个代码片段的重载不正确,但是 CancellationToken.None,TaskContinuationOptions.AttachedToParent,TaskScheduler.FromCurrentSynchronizationContext() 可以编译,并且上下文被传递。CancellationToken.None 正确吗?第二个代码片段也不完全正确。 - user247702
在第二个片段中,request未被定义,我不确定您在片段中想要输入什么。 - user247702
@Gusdor:每个方法都捕获自己的上下文,所以即使GetAsync使用了ConfigureAwait(false)await也会做正确的事情。没有必要使用ConfigureAwait(true) - Stephen Cleary
1
@Gusdor:非常抱歉。我试图在那篇文章的 每个“级别”的异步方法调用都有自己的上下文 部分解释清楚。虽然有点难以解释。 - Stephen Cleary
@StephenCleary 没问题。重新阅读后,现在非常清楚了。 - Gusdor
显示剩余2条评论

-1
你尝试过使用 TaskContinuationOptions.ExecuteSynchronously 吗?它应该会在同一个线程中运行继续任务...

http://msdn.microsoft.com/en-us/library/vstudio/system.threading.tasks.taskcontinuationoptions

“指定继续任务应该同步执行。使用此选项指定,继续将在导致前置任务转换为其最终状态的相同线程上运行。如果在创建继续时前置已经完成,则继续将在创建继续的线程上运行。只有非常短暂的继续应该同步执行。”


那并没有帮助,我已经尝试了TaskContinuationOptions.AttachedToParent | TaskContinuationOptions.ExecuteSynchronouslyTaskContinuationOptions.ExecuteSynchronously两种方式。 - user247702

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接