【问题标题】:How to cancel a Task executing a non managed C++ external routine如何取消执行非托管 C++ 外部例程的任务
【发布时间】:2014-12-26 14:08:34
【问题描述】:

我正在尝试修复 C# 异步代码,该代码启动在以非托管 C++ 例程编写的外部 dll 中执行的可取消操作。

如果用户委托调用外部非托管 C++ 例程,是否有办法使用在创建时传递给任务的取消令牌来取消任务?

据我所知,任务取消涉及用户委托和请求取消的代码之间的合作。成功的取消涉及调用 CancellationTokenSource.Cancel 方法的请求代码,并且用户委托及时终止操作,或者当他注意到已提出取消请求时从委托返回(通过轮询 CancellationToken.IsCancellationRequested方法)或使用 CancellationToken.ThrowIfCancellationRequested 方法抛出 OperationCanceledException。 (参见http://msdn.microsoft.com/en-us/library/dd997396%28v=vs.110%29.aspx

这两种方式涉及由用户委托执行的非托管 C++ 例程通过接收 CancellationToken 作为参数并定期调用其 IsCancellationRequested 和/或 ThrowIfCancellationRequested 方法来进行协作。

是否可以从非托管的外部 C++ 例程中做到这一点?

如果没有,当请求代码请求取消时,是否有办法强制终止执行用户委托(执行非托管 c++ 例程)的任务?

这是我尝试修复的混合 C#/C++Cli/非托管 C++ 代码的示例(摘录),以便能够取消用户委托在 C++ 非托管代码部分执行的操作:

FrmDemo.cs:-------------------------------------------------------- ------------------------------

public class FrmDemo : Form
{
    private CliClass m_CliObject;
    private System.Threading.CancellationTokenSource m_Cts;
    private System.Threading.CancellationToken m_Ct;

    private void FrmDemo_Load(object sender, EventArgs e)
    {
        // Creating the external CliObject:
        this.m_CliObject = new NSDemo.CliClass();
        ...
    }

    // Event handler of the button starting the cancelable asynchrone operation:
    private async void btnStart_Click(object sender, EventArgs e)
    {
        m_Cts = new System.Threading.CancellationTokenSource();
        m_Ct = m_Cts.Token;
        await Task.Factory.StartNew(() =>
        {
              // Launching a cancelable operation performed by a managed C++Cli Object :
              this.m_CliObject.DoSomething();   // How to eventually pass the CancellationToken m_ct to the m_CliObject ?
        }, m_ct);
        ...
    }


    //Event handler of the cancel button:
    private void btnCancel_Click(object sender, EventArgs e)
    {
        // Requesting cancellation:
        m_Cts.Cancel();
        // (Or alternatively, how to eventually force the termination of the async Task without collaboration from it ?)
    }

CliClass.h:------------------------------- ----------

#include "DemoCore.h"

using namespace System;
using namespace System::Runtime::InteropServices;
using namespace cli;

namespace NSDemo
{
    public ref class CliClass
    {

    public:

        CliClass();

        ~CliClass(); 

        void DoSomething()
        {
            // Performing the operation in the unmanaged coreObject:
            _coreObject->DoSomething();
        }

    private:
        UNSDemo::CoreClass *_coreObject;
        bool _disposed;

    };
}

CliClass.cpp:------------------------------------------

namespace NSDemo
{
    CliClass::CliClass()
    {
         _coreObject = new UNSDemo::CoreClass(...);
        ....
    }

    CliClass::~CliClass()
    {
        if (_disposed)
            return;               
        if (_coreObject != nullptr) {
            delete _coreObject;
            _coreObject = nullptr;
        }
        _disposed = true;
        GC::SuppressFinalize(this);
    }

CoreClass.h-----------------------------------------------------------------

namespace UNSDemo {

    class __declspec(dllexport) CoreClass {
    public:
        ScanningCore();

        ~ScanningCore();

        void DoSomething();

    private:

    ...

    };

}

CoreClass.cpp:-------------------------------------------------------- ----------------------------------

#include "CoreClass.h"

namespace UNSDemo {

    CoreClass::CoreClass()
    {
        ...
    }

    CoreClass::~CoreClass()
    {
        ...
    }

    // Method actually performing the cancelable operation:
    void CoreClass::DoSomething()
    {
        // Main loop of the unmanaged cancelable operation:
        while (...) {
            ...
            // How to check the cancellation request from here ? (How to access the CancellationToken ?)
            // and if cancellation is requested, how to eventually throw the OperationCanceledException ?

        }
    }
}

感谢您的帮助。

【问题讨论】:

    标签: c# c++-cli interop task cancellation


    【解决方案1】:

    如果您处理的是纯非托管代码,它不知道 CancellationToken 类,因此您不能像处理托管代码那样传递它。

    我要做的是声明您的非托管方法以获取指向布尔值的指针,如果布尔值设置为 true,则非托管代码会自行中止。在您的包装器中,使用 CancellationToken.Register 注册一个回调,该回调将在取消 CancellationToken 时将布尔值设置为 true。

    表面上这听起来很简单,但它有点复杂,因为您需要一个托管事件处理程序,该处理程序可以访问允许您获取地址的布尔值。

    public ref class CancelableTaskWrapper
    {
    private:
        bool* isCanceled;
        void (*unmanagedFunctionPointer)(bool*);
    
        void Canceled() { if (isCanceled != nullptr) *isCanceled = true; }
    
    public:
        CancelableTaskWrapper(void (*unmanagedFunctionPointer)(bool*))
        {
            this->unmanagedFunctionPointer = unmanagedFunctionPointer;
    
            isCanceled = new bool;
        }
    
        ~CancelableTaskWrapper() { if (isCanceled != nullptr) delete isCanceled; isCanceled = nullptr; }
        !CancelableTaskWrapper() { if (isCanceled != nullptr) delete isCanceled; isCanceled = nullptr; }
    
        void RunTask(CancellationToken cancelToken)
        {
            *isCanceled = false;
            CancellationTokenRegistration reg = cancelToken.Register(
                gcnew Action(this, &CancelableTaskWrapper::Canceled));
            unmanagedFunctionPointer(isCanceled);
        }
    };
    
    void someUnmanagedFunction(bool* isCanceled)
    {
        doSomethingLongRunning();
        if(*isCanceled) return;
        doSomethingLongRunning();
    }
    
    • 因为isCanceled 是一个指向布尔值的指针,所以它在堆上。因此,我们可以将指针传递给它,而无需执行任何特殊操作(例如,固定托管对象)。
    • CancellationTokenRegistration 实现了IDisposable,当reg 超出范围时,它会自动注销自己。 (您可以使用 C# 中的 using 语句来执行此操作。)

    免责声明:我现在不在编译器;可能存在语法错误。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-03-04
      • 2018-10-05
      • 2017-05-16
      • 2012-04-22
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多