Pass an argument to task in C++/CLI?

2020-07-03 09:27发布

问题:

I have this code for the C# in Visual Studio 2012.

public Task SwitchLaserAsync(bool on)
{
   return Task.Run(new Action(() => SwitchLaser(on)));
}

This will execute SwitchLaser method (public nonstatic member of a class MyClass) as a task with argument bool on.

I would like to do something similar in managed C++/CLI. But I am not able to find out any way how to run a task, which will execute a member method taking one parameter.

Current solution is like this:

Task^ MyClass::SwitchLaserAsync( bool on )
{
    laserOn = on;   //member bool 
    return Task::Run(gcnew Action(this, &MyClass::SwitchLaserHelper));
}

Implementation of SwitchLaserHelper function:

void MyClass::SwitchLaserHelper()
{
     SwitchLaser(laserOn);
}

There must be some solution like in C# and not to create helper functions and members (this is not threadsafe).

回答1:

There isn't yet any way to do this.

In C# you have a closure. When your C++/CLI compiler was written, the standardized syntax for closures in C++ was still being discussed. Thankfully, Microsoft chose to wait and use the standard lambda syntax instead of introducing yet another unique syntax. Unfortunately, it means the feature isn't yet available. When it is, it will look something like:

gcnew Action([this, on](){ SwitchLaser(on) });

The current threadsafe solution is to do what the C# compiler does -- put the helper function and data members not into the current class, but into a nested subtype. Of course you'll need to save the this pointer in addition to your local variable.

ref class MyClass::SwitchLaserHelper
{
    bool laserOn;
    MyClass^ owner;

public:
    SwitchLaserHelper(MyClass^ realThis, bool on) : owner(realThis), laserOn(on) {}
    void DoIt() { owner->SwitchLaser(laserOn); }
};

Task^ MyClass::SwitchLaserAsync( bool on )
{
    return Task::Run(gcnew Action(gcnew SwitchLaserHelper(this, on), &MyClass::SwitchLaserHelper::DoIt));
}

The C++ lamdba syntax will simply create that helper class for you (currently it works for native lambdas, but not yet for managed ones).



回答2:

Here's generic code I wrote this afternoon which might help (although it's not an exact match for this question). Maybe this will help the next person who stumbles onto this question.

generic<typename T, typename TResult>
ref class Bind1
{
    initonly T arg;
    Func<T, TResult>^ const f;
    TResult _() { return f(arg); }

public:
    initonly Func<TResult>^ binder;
    Bind1(Func<T, TResult>^ f, T arg) : f(f), arg(arg) {
        binder = gcnew Func<TResult>(this, &Bind1::_);
    }
};

ref class Binder abstract sealed // static
{
public:
    generic<typename T, typename TResult>
    static Func<TResult>^ Create(Func<T, TResult>^ f, T arg) {
        return (gcnew Bind1<T, TResult>(f, arg))->binder;
    }
};

Usage is

const auto f = gcnew Func<T, TResult>(this, &MyClass::MyMethod);
return Task::Run(Binder::Create(f, arg));


回答3:

Here's the working answer.. Have tested it.. Passing an argument (int) to the action sampleFunction.

#include "stdafx.h"
#include "CLRSamples.h"

using namespace System;
using namespace System::Threading;
using namespace System::Threading::Tasks;
using namespace System::Collections;
using namespace System::Collections::Generic;

void CLRSamples::sampleFunction(Object^ number)
{
    Console::WriteLine(number->ToString());
    Thread::Sleep((int)number * 100);
}

void CLRSamples::testTasks()
{
    List<Task^>^ tasks = gcnew List<Task^>();

    for (int i = 0; i < 10; i++)
    {
        tasks->Add(Task::Factory->StartNew((Action<Object^>^)(gcnew Action<Object^>(this, &CLRSamples::sampleFunction)), i));
    }

    Task::WaitAll(tasks->ToArray());

    Console::WriteLine("Completed...");
}

int main(array<System::String ^> ^args)
{
    CLRSamples^ samples = gcnew CLRSamples();
    samples->testTasks();

    Console::Read();
    return 0;
}


回答4:

I had a similar problem when I wanted to provide a parameter to a task executing a method which does not return a value (retuns void). Because of that Func<T, TResult> was not an option I could use. For more information, please check the page Using void return types with new Func.

So I ended up with a solution where I created a helper class

template <typename T>
ref class ActionArguments
{
public:
    ActionArguments(Action<T>^ func, T args) : m_func(func), m_args(args) {};
    void operator()() { m_func(m_args); };

private:
    Action<T>^ m_func;
    T m_args;
};

which is using Action<T> delegate to encapsulate a method that has a single parameter and does not return a value.

I would then use this helper class in a following way

ref class DisplayActivationController
{
public:
    DisplayActivationController();

    void StatusChanged(EventArgs^ args) { };
}


Action<EventArgs^>^ action =
    gcnew Action<EventArgs^>(this, &DisplayActivationController::StatusChanged);
ActionArguments<EventArgs^>^ action_args =
    gcnew ActionArguments<EventArgs^>(action, args);
Threading::Tasks::Task::Factory->
    StartNew(gcnew Action(action_args, &ActionArguments<EventArgs^>::operator()));

Approach using the helper class is probably not the most elegant solution, but is the best one I could find to be used in C++/CLI which does not support lambda expressions.