jjzjj

c# - 对 CancellationTokenSource.Cancel 的调用永远不会返回

coder 2024-06-01 原文

我有一种情况,对 CancellationTokenSource.Cancel 的调用永远不会返回。相反,在 Cancel 被调用之后(在它返回之前),执行将继续执行被取消代码的取消代码。如果被取消的代码随后没有调用任何可等待的代码,那么最初调用 Cancel 的调用者永远不会取回控制权。这很奇怪。我希望 Cancel 简单地记录取消请求并立即返回独立于取消本身。事实上,调用 Cancel 的线程最终会执行属于被取消操作的代码,并且在返回给 Cancel 的调用者之前这样做看起来像框架中的错误。

这是怎么回事:

  1. 有一段代码,我们称之为“工作代码”,它正在等待一些异步代码。为简单起见,假设此代码正在等待 Task.Delay:

    try
    {
        await Task.Delay(5000, cancellationToken);
        // … 
    }
    catch (OperationCanceledException)
    {
        // ….
    }
    

就在“工作代码”调用 Task.Delay 之前,它正在线程 T1 上执行。 continuation(即“await”之后的行或 catch 内的 block )稍后将在 T1 或其他线程上执行,具体取决于一系列因素。

  1. 还有一段代码,我们称之为“客户端代码”,它决定取消 Task.Delay。此代码调用 cancellationToken.Cancel。对 Cancel 的调用是在线程 T2 上进行的。

我希望线程 T2 通过返回到 Cancel 的调用者来继续。我还希望看到 catch (OperationCanceledException) 的内容很快在线程 T1 或 T2 以外的某个线程上执行。

接下来发生的事情令人惊讶。我看到在线程 T2 上,调用 Cancel 后,执行会立即继续 catch (OperationCanceledException) 中的 block 。当 Cancel 仍在调用堆栈中时,就会发生这种情况。就好像对 Cancel 的调用被它被取消的代码劫持了一样。下面是显示此调用堆栈的 Visual Studio 屏幕截图:

更多上下文

这里有一些关于实际代码作用的更多上下文: 有一个累积请求的“ worker 代码”。一些“客户端代码”正在提交请求。每隔几秒钟,“工作代码”就会处理这些请求。已处理的请求将从队列中删除。 然而,偶尔,“客户端代码”决定它到达了它希望立即处理请求的地步。为了将此信息传达给“工作代码”,它调用了“工作代码”提供的方法 Jolt。由“客户端代码”调用的方法 Jolt 通过取消由 worker 代码主循环执行的 Task.Delay 来实现此功能。工作人员的代码已取消其 Task.Delay 并继续处理已排队的请求。

实际代码被简化为最简单的形式,代码为 available on GitHub .

环境

此问题可以在控制台应用程序、Windows 通用应用程序后台代理和 Windows Phone 8.1 通用应用程序后台代理程序中重现。

此问题无法在适用于 Windows 的通用应用程序中重现,其中代码按我预期的方式工作,并且对 Cancel 的调用会立即返回。

最佳答案

CancellationTokenSource.Cancel 不只是设置 IsCancellationRequested 标志。

CancallationToken 类有一个 Register method ,它允许您注册将在取消时调用的回调。这些回调由 CancellationTokenSource.Cancel 调用。

让我们来看看source code :

public void Cancel()
{
    Cancel(false);
}

public void Cancel(bool throwOnFirstException)
{
    ThrowIfDisposed();
    NotifyCancellation(throwOnFirstException);            
}

这是 NotifyCancellation方法:

private void NotifyCancellation(bool throwOnFirstException)
{
    // fast-path test to check if Notify has been called previously
    if (IsCancellationRequested)
        return;

    // If we're the first to signal cancellation, do the main extra work.
    if (Interlocked.CompareExchange(ref m_state, NOTIFYING, NOT_CANCELED) == NOT_CANCELED)
    {
        // Dispose of the timer, if any
        Timer timer = m_timer;
        if(timer != null) timer.Dispose();

        //record the threadID being used for running the callbacks.
        ThreadIDExecutingCallbacks = Thread.CurrentThread.ManagedThreadId;

        //If the kernel event is null at this point, it will be set during lazy construction.
        if (m_kernelEvent != null)
            m_kernelEvent.Set(); // update the MRE value.

        // - late enlisters to the Canceled event will have their callbacks called immediately in the Register() methods.
        // - Callbacks are not called inside a lock.
        // - After transition, no more delegates will be added to the 
        // - list of handlers, and hence it can be consumed and cleared at leisure by ExecuteCallbackHandlers.
        ExecuteCallbackHandlers(throwOnFirstException);
        Contract.Assert(IsCancellationCompleted, "Expected cancellation to have finished");
    }
}

好的,现在要注意的是 ExecuteCallbackHandlers 可以在目标上下文或当前上下文中执行回调。我让你看看ExecuteCallbackHandlers method source code因为在这里包含它有点太长了。但有趣的是:

if (m_executingCallback.TargetSyncContext != null)
{

    m_executingCallback.TargetSyncContext.Send(CancellationCallbackCoreWork_OnSyncContext, args);
    // CancellationCallbackCoreWork_OnSyncContext may have altered ThreadIDExecutingCallbacks, so reset it. 
    ThreadIDExecutingCallbacks = Thread.CurrentThread.ManagedThreadId;
}
else
{
    CancellationCallbackCoreWork(args);
}

我猜你现在开始明白我接下来要看的地方了……当然是Task.Delay。让我们看看它的source code :

// Register our cancellation token, if necessary.
if (cancellationToken.CanBeCanceled)
{
    promise.Registration = cancellationToken.InternalRegisterWithoutEC(state => ((DelayPromise)state).Complete(), promise);
}

嗯……那是什么InternalRegisterWithoutEC method

internal CancellationTokenRegistration InternalRegisterWithoutEC(Action<object> callback, Object state)
{
    return Register(
        callback,
        state,
        false, // useSyncContext=false
        false  // useExecutionContext=false
     );
}

唉。 useSyncContext=false - 这解释了您看到的行为,因为 ExecuteCallbackHandlers 中使用的 TargetSyncContext 属性将为 false。由于未使用同步上下文,取消在 CancellationTokenSource.Cancel 的调用上下文中执行。

关于c# - 对 CancellationTokenSource.Cancel 的调用永远不会返回,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/31495411/

有关c# - 对 CancellationTokenSource.Cancel 的调用永远不会返回的更多相关文章

  1. ruby - 为什么 4.1%2 使用 Ruby 返回 0.0999999999999996?但是 4.2%2==0.2 - 2

    为什么4.1%2返回0.0999999999999996?但是4.2%2==0.2。 最佳答案 参见此处:WhatEveryProgrammerShouldKnowAboutFloating-PointArithmetic实数是无限的。计算机使用的位数有限(今天是32位、64位)。因此计算机进行的浮点运算不能代表所有的实数。0.1是这些数字之一。请注意,这不是与Ruby相关的问题,而是与所有编程语言相关的问题,因为它来自计算机表示实数的方式。 关于ruby-为什么4.1%2使用Ruby返

  2. ruby - Highline 询问方法不会使用同一行 - 2

    设置:狂欢ruby1.9.2高线(1.6.13)描述:我已经相当习惯在其他一些项目中使用highline,但已经有几个月没有使用它了。现在,在Ruby1.9.2上全新安装时,它似乎不允许在同一行回答提示。所以以前我会看到类似的东西:require"highline/import"ask"Whatisyourfavoritecolor?"并得到:Whatisyourfavoritecolor?|现在我看到类似的东西:Whatisyourfavoritecolor?|竖线(|)符号是我的终端光标。知道为什么会发生这种变化吗? 最佳答案

  3. ruby-on-rails - 项目升级后 Pow 不会更改 ruby​​ 版本 - 2

    我在我的Rails项目中使用Pow和powifygem。现在我尝试升级我的ruby​​版本(从1.9.3到2.0.0,我使用RVM)当我切换ruby​​版本、安装所有gem依赖项时,我通过运行railss并访问localhost:3000确保该应用程序正常运行以前,我通过使用pow访问http://my_app.dev来浏览我的应用程序。升级后,由于错误Bundler::RubyVersionMismatch:YourRubyversionis1.9.3,butyourGemfilespecified2.0.0,此url不起作用我尝试过的:重新创建pow应用程序重启pow服务器更新战俘

  4. ruby - 检查字符串是否包含散列中的任何键并返回它包含的键的值 - 2

    我有一个包含多个键的散列和一个字符串,该字符串不包含散列中的任何键或包含一个键。h={"k1"=>"v1","k2"=>"v2","k3"=>"v3"}s="thisisanexamplestringthatmightoccurwithakeysomewhereinthestringk1(withspecialcharacterslike(^&*$#@!^&&*))"检查s是否包含h中的任何键的最佳方法是什么,如果包含,则返回它包含的键的值?例如,对于上面的h和s的例子,输出应该是v1。编辑:只有字符串是用户定义的。哈希将始终相同。 最佳答案

  5. 使用 ACL 调用 upload_file 时出现 Ruby S3 "Access Denied"错误 - 2

    我正在尝试编写一个将文件上传到AWS并公开该文件的Ruby脚本。我做了以下事情:s3=Aws::S3::Resource.new(credentials:Aws::Credentials.new(KEY,SECRET),region:'us-west-2')obj=s3.bucket('stg-db').object('key')obj.upload_file(filename)这似乎工作正常,除了该文件不是公开可用的,而且我无法获得它的公共(public)URL。但是当我登录到S3时,我可以正常查看我的文件。为了使其公开可用,我将最后一行更改为obj.upload_file(file

  6. c# - 如何在 ruby​​ 中调用 C# dll? - 2

    如何在ruby​​中调用C#dll? 最佳答案 我能想到几种可能性:为您的DLL编写(或找人编写)一个COM包装器,如果它还没有,则使用Ruby的WIN32OLE库来调用它;看看RubyCLR,其中一位作者是JohnLam,他继续在Microsoft从事IronRuby方面的工作。(估计不会再维护了,可能不支持.Net2.0以上的版本);正如其他地方已经提到的,看看使用IronRuby,如果这是您的技术选择。有一个主题是here.请注意,最后一篇文章实际上来自JohnLam(看起来像是2009年3月),他似乎很自在地断言RubyCL

  7. ruby - Ruby 中的隐式返回值是怎么回事? - 2

    所以我开始关注ruby​​,很多东西看起来不错,但我对隐式return语句很反感。我理解默认情况下让所有内容返回self或nil但不是语句的最后一个值。对我来说,它看起来非常脆弱(尤其是)如果你正在使用一个不打算返回某些东西的方法(尤其是一个改变状态/破坏性方法的函数!),其他人可能最终依赖于一个返回对方法的目的并不重要,并且有很大的改变机会。隐式返回有什么意义?有没有办法让事情变得更简单?总是有返回以防止隐含返回被认为是好的做法吗?我是不是太担心这个了?附言当人们想要从方法中返回特定的东西时,他们是否经常使用隐式返回,这不是让你组中的其他人更容易破坏彼此的代码吗?当然,记录一切并给出

  8. C# 到 Ruby sha1 base64 编码 - 2

    我正在尝试在Ruby中复制Convert.ToBase64String()行为。这是我的C#代码:varsha1=newSHA1CryptoServiceProvider();varpasswordBytes=Encoding.UTF8.GetBytes("password");varpasswordHash=sha1.ComputeHash(passwordBytes);returnConvert.ToBase64String(passwordHash);//returns"W6ph5Mm5Pz8GgiULbPgzG37mj9g="当我在Ruby中尝试同样的事情时,我得到了相同sha

  9. ruby - 通过 RVM 安装 Ruby 1.9.2 永远行不通! - 2

    当我执行>rvminstall1.9.2时一切顺利。然后我做>rvmuse1.9.2也很顺利。但是当涉及到ruby​​-v时..sam@sjones:~$rvminstall1.9.2/home/sam/.rvm/rubies/ruby-1.9.2-p136,thismaytakeawhiledependingonyourcpu(s)...ruby-1.9.2-p136-#fetchingruby-1.9.2-p136-#downloadingruby-1.9.2-p136,thismaytakeawhiledependingonyourconnection...%Total%Rece

  10. java - 从 JRuby 调用 Java 类的问题 - 2

    我正在尝试使用boilerpipe来自JRuby。我看过guide从JRuby调用Java,并成功地将它与另一个Java包一起使用,但无法弄清楚为什么同样的东西不能用于boilerpipe。我正在尝试基本上从JRuby中执行与此Java等效的操作:URLurl=newURL("http://www.example.com/some-location/index.html");Stringtext=ArticleExtractor.INSTANCE.getText(url);在JRuby中试过这个:require'java'url=java.net.URL.new("http://www

随机推荐