如何在C++/CLI中向任务(task)传递参数?

14

我在Visual Studio 2012中有这段C#代码。

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

这将执行类 MyClass 的公共非静态成员方法 SwitchLaser,并将 bool 类型的参数 on 作为任务执行。

我想在托管的C++/CLI中做类似的事情。但我无法找到任何方法来运行一个任务,该任务将执行一个带有一个参数的成员方法。

当前的解决方案如下:

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

SwitchLaserHelper函数的实现:

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

必须有一些类似于C#的解决方案,而不是创建帮助函数和成员(这不是线程安全的)。

5个回答

17

目前还没有任何方法可以做到这一点。

在C#中,你有一个闭包(closure)。当你的C++/CLI编译器被写出来时,C++中用于闭包的标准语法仍在讨论中。幸运的是,微软选择等待并使用标准lambda语法,而不是引入另一个独特的语法。不幸的是,这意味着该功能尚不可用。当它可用时,它将类似于:

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

目前的线程安全解决方案是做C#编译器所做的--将辅助函数和数据成员放在嵌套子类型中,而不是当前类中。当然,除了本地变量外,您还需要保存this指针。

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));
}

C++的Lambda语法将为您创建该辅助类(目前对本地lambda有效,但对托管lambda尚未有效)。


感谢您的答案。很明显Lambda在管理代码中不受支持。尽管如此,我仍希望避免创建辅助类。在文档中,我发现了用于托管C++的Action<T>类。这应该能够接收一个参数的函数,但我无法使用它,特别是与Task::Run方法一起使用(或以某种方式与Task类一起使用)。是否有没有辅助类或辅助方法的解决方案? - Bezousek
@Bezousek: Task::Run 需要一个 Action 委托。不,Action<T> 不是相同的类型。闭包是使用辅助类实现的。这就是 C# 编译器从你在问题中的示例代码创建的内容,如果管理的 lambda 特性被添加,C++/CLI 编译器将会在未来执行相同的操作。 - Ben Voigt
据我所知,C++/CLI仍不支持从闭包创建托管委托。 - Ben Voigt
@BenVoigt - 直接不行,但是一个带有闭包的Lambda可以是本地的,然后被包装在std:function中,然后该函数可以从(无参数的)Action中调用。听起来很丑陋,但实际上只需要2-3行代码。 - David V. Corbin
@BenVoigt - 同意“有点麻烦”。但仍然比这里展示的(现在已经十年历史的)额外类/模板/等等要好2-3行。因此,我认为取得了重大进展(尽管仍远非无缝)。我尽量将我的C++/CLI减少到最小的互操作性,并且不会在C++代码中大量使用“Slamming”/clr(或等效)开关。几十年来,这样做收效甚佳。 - David V. Corbin
显示剩余6条评论

5

这是我今天下午写的通用代码,可能对这个问题并不完全匹配。也许会帮助下一个遇到这个问题的人。

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;
    }
};

使用方法如下:

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

3

这是可行的答案...我已经测试过了...将一个参数(int)传递给操作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;
}

谢谢您的回答,我有一个问题,如何将多个参数传递给函数? - Milind Morey
@MilindMorey 不可能传递多个参数,因此唯一的解决方案是传递一个具有这些参数作为实例变量的类的对象(实例)。 - Viswanadh
感谢Viswanadh的回复,我正在尝试依次调用多个函数,就像您在上面的示例中使用for循环一样,唯一的区别是我需要等待一段时间并运行下一个任务;有没有办法按特定时间间隔依次运行函数? - Milind Morey
@MilindMorey 很抱歉回复晚了,我没有跟进。我不认为我理解你的问题。但是,我会回答我所理解的问题。为什么不在将其添加到任务之前,在for循环中加入一个sleep呢?这样行不行? - Viswanadh

2

当我想为执行一个不返回值(即返回void)的方法提供参数时,我遇到了类似的问题。因此, Func<T,TResult> 不是我可以使用的选项。有关更多信息,请查看页面“使用新Func与void返回类型”

于是,我最终采用了创建辅助类的解决方案。

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;
};

这段代码使用了 Action<T> 委托来封装一个只有一个参数且没有返回值的方法。

接下来,我会按照以下方式使用这个帮助类:

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()));

使用helper类的方法可能不是最优雅的解决方案,但这是我在C++/CLI中找到的最好的解决方案,因为它不支持lambda表达式。


0
如果您正在使用C++/CLR,则可以制作一个C# DLL并将其添加为引用。
namespace TaskClrHelper
{
  public static class TaskHelper
  {
    public static Task<TResult> StartNew<T1, TResult>(
     Func<T1, TResult> func,
     T1 arg)
      => Task.Factory.StartNew(() => func(arg));

    public static Task<TResult> StartNew<T1, T2, TResult>(
     Func<T1, T2, TResult> func,
     T1 arg1, T2 arg2)
      => Task.Factory.StartNew(() => func(arg1, arg2));
  }
}

        bool Device::Stop(int timeout)
        {
            _ResetEvent_Running->Set();
            return _ResetEvent_Disconnect->WaitOne(timeout);
        }
        Task<bool>^ Device::StopAsync(int timeout)
        {
            auto func = gcnew Func<int, bool>(this, &Device::Stop);
            return TaskClrHelper::TaskHelper::StartNew<int,bool>(func,timeout);
        }

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接