Reputation: 63
I'm trying to fix C# async code launching a cancelable operation performed in an external dll written in unmanaged C++ routine.
Is there a way to cancel a Task using a Cancellation Token passed to the Task at creation, if the user delegate calls an external non managed C++ routine ?
As far I know, Task cancellation involves cooperation between the user delegate and the code that requested the cancellation. A successful cancellation involves the requesting code calling the CancellationTokenSource.Cancel method, and the user delegate terminating the operation in a timely manner either by simply returning from the delegate when he notices that the a cancellation request has been raised (by polling the CancellationToken.IsCancellationRequested method) or by throwing an OperationCanceledException using CancellationToken.ThrowIfCancellationRequested method. (cf http://msdn.microsoft.com/en-us/library/dd997396%28v=vs.110%29.aspx)
These two ways involve that the non managed C++ routine executed by the user delegate cooperates by receiving the CancellationToken as a parameter and by calling at regular intervals its IsCancellationRequested and/or ThrowIfCancellationRequested methods.
Is that possible to do that from a non managed external C++ routine ?
If not, is there a way to force the termination of the task executing the user delegate (executing the non managed c++ routine) when the cancellation is requested by the requesting code ?
Here is an example (extract) of the mixed C# / C++Cli / Unmanaged C++ code I'm trying to fix in order to be able to cancel the operation performed by the user delegate in the C++ unmanaged code part:
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 ?
}
}
}
Thank you for any help.
Upvotes: 5
Views: 3493
Reputation: 27864
If you're dealing with purely unmanaged code, it doesn't know about the CancellationToken class, so you can't pass it around like you would with managed code.
What I would do is declare your unmanaged method to take a pointer to a Boolean, and have the unmanaged code abort itself if the boolean is set true. In your wrapper, use CancellationToken.Register to register a callback that will set the Boolean to true when the CancellationToken is cancelled.
This sounds easy on the surface, but it's a bit complex because you need a managed event handler that can access a Boolean value that you're allowed to take the address of.
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
is a pointer to a bool, it's on the heap. Therefore, we're allowed to pass a pointer to it without needing to do anything special (e.g, pinning the managed object).CancellationTokenRegistration
implements IDisposable
, it will automatically unregister itself when reg
goes out of scope. (You'd do this with a using
statement in C#.)Disclaimer: I'm not at a compiler right now; there may be syntax errors.
Upvotes: 5