【问题标题】:Continuation Task in the same thread as previous与之前相同的线程中的继续任务
【发布时间】:2012-12-13 00:33:05
【问题描述】:

我有一个创建任务和延续任务的 WebService。

在第一个任务中我们设置Thread.CurrentPrincipal

因此,当 ContinuationTask 启动时,它不再具有 Thread.CurrentPrincipal。

我想在 ContinuationTask 中指定它应该在与其前项相同的线程中运行

我在网上搜索过,但我只发现了线程在 SynchronizationContext 中运行的要求,因此我开始认为我缺少一些基本规则,特别是关于 Thread.Principal 应该如何工作的规则。

【问题讨论】:

  • 将任务绑定到线程是一个坏主意,并且在出现异常时容易出错。与其修复线程的主体并要求所有任务使用同一线程,不如尝试将 WindowsIDentity 或令牌对象作为状态传递给任务,并在每个任务中模拟用户。否则,如果发生异常并且忘记清除身份,您将面临更改 ThreadPool 身份的风险
  • 附言。您使用的是什么类型的身份? WindowsIdentity 还是其他?
  • 我们实现了自己的 IPrincipal,并且是应用程序本身进行身份验证。似乎最好的办法是在任务中传递 IPrincipal。

标签: c# task-parallel-library impersonation


【解决方案1】:

首先,不要将TaskContinuationOptions.ExecuteSynchronously用于此目的!您不能在同一线程上强制继续。它只适用于非常高的概率。总有它不起作用的情况:过多的递归会导致 TPL 不能同步执行。自定义TaskSchedulers 也没有义务支持这一点。

这是一种常见的误解,尤其是因为它在网络上被错误地传播。以下是有关该主题的一些阅读材料:http://blogs.msdn.com/b/pfxteam/archive/2012/02/07/10265067.aspx

如果您需要在同一个线程上运行,请执行以下操作:

Task.Factory.StartNew(() => { First(); Second(); });

这么简单。

让我通过展示一个替代解决方案来说明为什么会这样:

void MyCompositeTask()
{
  var result = First();
  Second(result);
}
Task.Factory.StartNew(() => MyCompositeTask());

这看起来更直观:我们将 MyCompositeTask 传递给 TPL 以运行。 TPL 不关心我们在回调中做了什么。我们可以做任何我们想做的事情,包括调用多个方法并传递结果。

【讨论】:

  • 无意冒犯,但我认为这本书的作者(被许多人认为是 C# 4.0 的权威参考)是比您更可靠的资源。请提供一些参考或阅读材料,说明您提供的解决方案如何确保以满足 OP 正在寻找的行为的方式调用这 2 个方法。
  • 我并没有傲慢到不承认这是一个更合适的解决方案,但是当你告诉我一本关于该主题的著名书籍的作者是“错误的”时,我想多看一点证据。在某些时候,99.999999% 足以被认为是 100。在这种情况下,您所说的方差似乎不值得考虑,尤其是当深度递归在这里没有影响时
  • @JesseCarter 你不认为一个接一个地执行一个方法会保证主体持续存在吗? Everything 在普通方法调用之间持续存在。深度递归经常发生在潜在的无限延续链中。以下是 ExecSync 不生效的多种情况:blogs.msdn.com/b/pfxteam/archive/2012/02/07/10265067.aspx(可信来源)。
【解决方案2】:

来自我的 C# 教科书(C# 4.0 in a Nutshell):

您可以通过在调用ContinueWith 时指定TaskContinuationOptions.ExecuteSynchronously 来强制它们[延续任务] 在同一个线程[作为它们的先行者] 上执行:这可以通过减少间接性来提高非常细粒度的延续中的性能。

原则上我没有尝试过,但它似乎是您正在寻找的,可以与 Thread.CurrentPrincipal 结合使用。

这里是MSDN article 的链接以及一些更具体的示例

【讨论】:

【解决方案3】:

设置池线程的标识不是一个好主意。如果您忘记在异常处理程序中清除身份,它将您与此特定线程联系起来,并且有可能在发生异常时“泄露”身份。您最终可能会使用“泄露”的身份运行不相关的任务。

尝试将 WindowsIdentity 对象传递给任务并使用WindowsIdentity.Impersonate 进行模拟。这将允许您使用任何可用的线程,并且即使发生异常也会安全地清除身份。

你可以试试这样的:

WindowsPrincipal myPrincipal=...;
...
var identity=(WindowsIdentity)myPrincipal.Identity;
var task=Task.Factory.StartNew(ident=>{
        var id=(WindowsIdentity)ident;
        using(var context=id.Impersonate())
        {
            //Work using the impersonated identity here
        }
        return id;
    },identity).
.ContinueWith(r=>{
        var id = r.Result;
        using(var context=id.Impersonate())
        {
            //Work using the impersonated identity here
        }
});

using 语句确保即使发生异常也会清除模拟身份。

【讨论】:

    【解决方案4】:

    使用 TaskScheduler.FromCurrentSynchronizationContext() 调用延续:

    Task UITask= task.ContinueWith(() =>
    {
     this.TextBlock1.Text = "Complete"; 
    }, TaskScheduler.FromCurrentSynchronizationContext());
    

    复制自https://stackoverflow.com/a/4331287/503969

    【讨论】:

    • "InvalidOperationException 未处理:当前 SynchronizationContext 不能用作 TaskScheduler。" :-(
    猜你喜欢
    • 2014-01-19
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-01-12
    • 2014-09-21
    相关资源
    最近更新 更多