I'm writing a thread pool use C#.
This thread pool needs to support execute different type of function.
Below is what I need:
System.Threading.Tasks< TResult > that represents the return value of our callable packageIn CPP, I can do it with some template magic:
template <typename funtype, typename ...argstype>
std::future<typename std::result_of<funtype(argstype...)>::type> async(funtype&& func, argstype&&... args) {
//start function body↓
typedef std::packaged_task<std::result_of<funtype(argstype...)>::type(argstype...)> task_type;
auto task = std::make_shared<task_type>(std::forward<funtype>(func));
// bind to a callable object(functor) with signature void(void)
auto whatINeed= std::bind([task](argstype... args) mutable {
(*task)(std::forward<argstype>(args)...);
}, std::forward<argstype>(args)...);
//and we return the std::future which represents the return value of our package
//in C#, i need to return an Task<TResult>
return task->get_future();
}
In C#, now I wrote:
public Task<TResult> async<TResult>(Delegate func, params object[] args)
{
var stdPromiseXD = new TaskCompletionSource<TResult>();
// the lambda is a callable object with signature void(void)
works.Enqueue(() =>
{
try
{
stdPromiseXD.SetResult((TResult)func.DynamicInvoke(args));
}
catch (Exception ex)
{
stdPromiseXD.SetException(ex);
}
});
// return the Task which equals std::future in CPP
return stdPromiseXD.Task;
}
But this C# version is not good as CPP version. First its does not support non-return function, second the DynamicInvoke method could be significantly slow in some situation.
So can anyone tell me how to bind a function and arguments into a pack in C# more elegant?
I recommend using Func<TResult> and Action instead of delegate and then using closures in the calling code to simplify the usage. Func is a generic strongly typed delegate that returns a result and Action is a generic strongly typed delegate that doesn't return a result.
public Task<TResult> Enqueue<TResult>(Func<TResult> func)
{
var stdPromiseXD = new TaskCompletionSource<TResult>();
// the lambda is a callable object with signature void(void)
works.Enqueue(() =>
{
try
{
stdPromiseXD.SetResult((TResult)func());
}
catch (Exception ex)
{
stdPromiseXD.SetException(ex);
}
});
// return the Task which equals std::future in CPP
return stdPromiseXD.Task;
}
public Task Enqueue(Action action)
{
return Enqueue<object>(() =>
{
action();
return null;
});
}
I called this with:
var arg1 = "x1";
var arg2 = "2nd";
var arg3 = "third";
var resultTask1 = tp.Enqueue(() => DoConsoleWrite(arg1, arg2, arg3));
var resultTask2 = tp.Enqueue(() => SumAllNumbers(1, 2, 3, 4, 5));
var resultTask3 = tp.Enqueue(() => ThrowException());
while (tp.Pop()) { }
resultTask1.GetAwaiter().GetResult();
var result2 = resultTask2.GetAwaiter().GetResult();
var result3Exception = resultTask3.Exception;
The alternative to using closures is to create overloads for each parameter count of func (Func<TResult>, Func<T1,TResult>, Func<T1,T2,Result>, etc and and Action, Action<T1>, Action<T1,T2>, etc)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With