I have to map input from device hardware done with a native driver in that way:
GIS_LF_API.TSLF_SetReaderMode(handle, 1);
GIS_LF_API.TSLF_StartAutoRead(handle, '\r', AutoReadProc);
AutoReadProc is invoked when the input come from the hardware and is defined in that way to call my OnCallback method:
private GIS_LF_API.AutoReadCallback AutoReadProc = OnCallback;
private static int OnCallback(string Arr, int Len)
{
//do somethings
return intValue;
}
where AutoReadCallback into driver is defined like this:
public delegate int AutoReadCallback([MarshalAsAttribute(UnmanagedType.LPStr)] string pData, int Len);
now i want to map an Observable<int> when the event is called.
How is possible to do? I have tried with Observable.FromEventPattern but i have not find a way to map it.
You can either stick an Event around the delegate, then use Observable.FromEvent (probably preferred), or stick a Subject in your handler, and expose the subject as an observable.
Event solution:
public class X
{
public X()
{
AutoReadProc += OnCallback; //If still necessary.
IntObservable = Observable.FromEvent<GIS_LF_API.AutoReadCallback, int>(h => AutoReadProc += h, h => AutoReadProc -= h);
}
public event GIS_LF_API.AutoReadCallback AutoReadProc;
private int OnCallback(string Arr, int Len)
{
//do something, if necessary. Not required for observable.
var intValue = 0;
return intValue;
}
public IObservable<int> IntObservable { get; }
}
Subject solution:
public class Y
{
private readonly Subject<int> _subject = new Subject<int>();
public Y()
{
IntObservable = _subject.AsObservable();
}
private int OnCallback(string Arr, int Len)
{
//do something
var intValue = 0;
_subject.OnNext(intValue);
return intValue;
}
public IObservable<int> IntObservable { get; }
}
I made OnCallback non-static, which differs from your solution. It would still work if static, but the Subject would have to be static as well.
Have a go at this:
void Main()
{
var foo = new Foo();
Observable
.FromEvent<AutoReadCallback, int>(a => (pd, l) =>
{
var r = pd.Length + l;
a(r);
return r;
}, h => foo.AutoReadCallback += h, h => foo.AutoReadCallback -= h)
.Subscribe(x => Console.WriteLine(x));
foo.OnAutoReadCallback("Bar", 2);
}
public delegate int AutoReadCallback(string pData, int Len);
public class Foo
{
public event AutoReadCallback AutoReadCallback;
public int OnAutoReadCallback(string pData, int len)
{
var arc = this.AutoReadCallback;
if (arc != null)
{
return arc(pData, len);
}
return -1;
}
}
It's weird that an event handler returns a value so this code has some redundant looking code.
Related
I am preparing for an exam and I have to examine various codes. One is about delegates in C# - I'm failing to see what it does, since I don't know if you can put functions from two different classes in one delegate.
Here's the code:
namespace konzolnaApplikacijaDelegateVoidMain {
public delegate int MyDelegate(int x);
class Program
{
public int number;
public Program (int x)
{
number = x;
}
public int Add(int x)
{
return x + 10;
}
public int Substract(int x)
{
return x - 10;
}
public int Multiply(int x)
{
return x * 2;
}
static void Main(string[] args)
{
MyDelegate delegate;
Program first = new Program(20);
Program second = new Program(50);
delegate = first.Add;
delegate += second.Add;
delegate -= first.Substract;
delegate += second.Multiply;
delegate += first.Add;
delegate(first.number);
delegate(second.number);
Console.Write("{0}", first.number + second.number);
}
}
}
Delegates are quite simple. Consider the following implementation of a delegate.
namespace DelegateExamples
{
class Program
{
//Declare a integer delegate to handle the functions in class A and B
public delegate int MathOps(int a, int b);
static void Main(string[] args)
{
MathOps multiply = ClassA.Multiply;
MathOps add = ClassB.Add;
int resultA = multiply(30, 30);
int resultB = add(1000, 500);
Console.WriteLine("Results: " + resultA + " " + resultB);
Console.ReadKey();
}
}
public class ClassA
{
public static int Multiply(int a, int b)
{
return a * b;
}
}
public class ClassB
{
public static int Add(int a, int b)
{
return a + b;
}
}
}
I need to call a function in dll and return an array of structures. I do not know the size of the array in advance. How can this be done? The error can not marshal 'returns value' invalid managed / unmanaged
Code in C#:
[DllImport("CppDll"]
public static extern ResultOfStrategy[] MyCppFunc(int countO, Data[] dataO, int countF, Data[] dataF);
in C++:
extern "C" _declspec(dllexport) ResultOfStrategy* WINAPI MyCppFunc(int countO, MYDATA * dataO, int countF, MYDATA * dataF)
{
return Optimization(countO, dataO, countF, dataF);
}
Return array of struct :
struct ResultOfStrategy
{
bool isGood;
double allProfit;
double CAGR;
double DD;
int countDeals;
double allProfitF;
double CAGRF;
double DDF;
int countDealsF;
Param Fast;
Param Slow;
Param Stop;
Param Tp;
newStop stloss;
};
I'll give you two responses. The first one is a method quite basic. The second one is quite advanced.
Given:
C-side:
struct ResultOfStrategy
{
//bool isGood;
double allProfit;
double CAGR;
double DD;
int countDeals;
double allProfitF;
double CAGRF;
double DDF;
int countDealsF;
ResultOfStrategy *ptr;
};
C#-side:
public struct ResultOfStrategy
{
//[MarshalAs(UnmanagedType.I1)]
//public bool isGood;
public double allProfit;
public double CAGR;
public double DD;
public int countDeals;
public double allProfitF;
public double CAGRF;
public double DDF;
public int countDealsF;
public IntPtr ptr;
}
Note that I've removed the bool, because it has some problems with the case 2 (but it works with case 1)... Now...
Case 1 is very basic, and it will cause the .NET marshaler to copy the array built in C to a C# array.
Case 2 as I've written is quite advanced and it tries to bypass this marshal-by-copy and make so that the C and the .NET can share the same memory.
To check the difference I've written a method:
static void CheckIfMarshaled(ResultOfStrategy[] ros)
{
GCHandle h = default(GCHandle);
try
{
try
{
}
finally
{
h = GCHandle.Alloc(ros, GCHandleType.Pinned);
}
Console.WriteLine("ros was {0}", ros[0].ptr == h.AddrOfPinnedObject() ? "marshaled in place" : "marshaled by copy");
}
finally
{
if (h.IsAllocated)
{
h.Free();
}
}
}
And I've added a ptr field to the struct that contains the original address of the struct (C-side), to see if it has been copied or if it is the original struct.
Case 1:
C-side:
__declspec(dllexport) void MyCppFunc(ResultOfStrategy** ros, int* length)
{
*ros = (ResultOfStrategy*)::CoTaskMemAlloc(sizeof(ResultOfStrategy) * 2);
::memset(*ros, 0, sizeof(ResultOfStrategy) * 2);
(*ros)[0].ptr = *ros;
(*ros)[0].allProfit = 100;
(*ros)[1].ptr = *ros + 1;
(*ros)[1].allProfit = 200;
*length = 2;
}
and C#-side:
public static extern void MyCppFunc(
[MarshalAs(UnmanagedType.LPArray, ArraySubType = UnmanagedType.Struct, SizeParamIndex = 1)] out ResultOfStrategy[] ros,
out int length
);
and then:
ResultOfStrategy[] ros;
int length;
MyCppFunc(out ros, out length);
Console.Write("Case 1: ");
CheckIfMarshaled(ros);
ResultOfStrategy[] ros2;
The .NET marshaler knows (because we gave it the information) that the second parameter is the length of the out ResultOfStrategy[] ros (see the SizeParamIndex?), so it can create a .NET array and copy from the C-allocated array the data. Note that in the C code I've used the ::CoTaskMemAlloc to allocate the memory. The .NET wants the memory to be allocated with that allocator, because it then frees it. If you use malloc/new/??? to allocate the ResultOfStrategy[] memory, bad things will happen.
Case 2:
C-Side:
__declspec(dllexport) void MyCppFunc2(ResultOfStrategy* (*allocator)(size_t length))
{
ResultOfStrategy *ros = allocator(2);
ros[0].ptr = ros;
ros[1].ptr = ros + 1;
ros[0].allProfit = 100;
ros[1].allProfit = 200;
}
C#-side:
// Allocator of T[] that pins the memory (and handles unpinning)
public sealed class PinnedArray<T> : IDisposable where T : struct
{
private GCHandle handle;
public T[] Array { get; private set; }
public IntPtr CreateArray(int length)
{
FreeHandle();
Array = new T[length];
// try... finally trick to be sure that the code isn't interrupted by asynchronous exceptions
try
{
}
finally
{
handle = GCHandle.Alloc(Array, GCHandleType.Pinned);
}
return handle.AddrOfPinnedObject();
}
// Some overloads to handle various possible length types
// Note that normally size_t is IntPtr
public IntPtr CreateArray(IntPtr length)
{
return CreateArray((int)length);
}
public IntPtr CreateArray(long length)
{
return CreateArray((int)length);
}
public void Dispose()
{
FreeHandle();
}
~PinnedArray()
{
FreeHandle();
}
private void FreeHandle()
{
if (handle.IsAllocated)
{
handle.Free();
}
}
}
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
public delegate IntPtr AllocateResultOfStrategyArray(IntPtr length);
[DllImport("CplusPlusSide.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern void MyCppFunc2(
AllocateResultOfStrategyArray allocator
);
and then
ResultOfStrategy[] ros;
using (var pa = new PinnedArray<ResultOfStrategy>())
{
MyCppFunc2(pa.CreateArray);
ros = pa.Array;
// Don't do anything inside of here! We have a
// pinned object here, the .NET GC doesn't like
// to have pinned objects around!
Console.Write("Case 2: ");
CheckIfMarshaled(ros);
}
// Do the work with ros here!
Now this one is interesting... The C function receives an allocator from the C#-side (a function pointer). This allocator will allocate length elements and must then remember the address of the allocated memory. The trick here is that C#-side we allocate a ResultOfStrategy[] of the size required by C, that is then used directly C-side. This will break badly if ResultOfStrategy isn't blittable (a term meaning that you can only use some types inside ResultOfStrategy, mainly numerical types, no string, no char, no bool, see here). The code is quite advanced because on top of all of this, it has to use the GCHandle to pin the .NET array, so that it isn't moved around. Handling this GCHandle is quite complex, so I had to create a ResultOfStrategyContainer that is IDisposable. In this class I even save the reference to the created array (the ResultOfStrategy[] ResultOfStrategy). Note the use of the using. That is the correct way to use the class.
bool and case 2
As I've said, while bool work with case 1, they don't work with case 2... But we can cheat:
C-side:
struct ResultOfStrategy
{
bool isGood;
C#-side:
public struct ResultOfStrategy
{
private byte isGoodInternal;
public bool isGood
{
get => isGoodInternal != 0;
set => isGoodInternal = value ? (byte)1 : (byte)0;
}
this works:
C-side:
extern "C"
{
struct ResultOfStrategy
{
bool isGood;
double allProfit;
double CAGR;
double DD;
int countDeals;
double allProfitF;
double CAGRF;
double DDF;
int countDealsF;
ResultOfStrategy *ptr;
};
int num = 0;
int size = 10;
__declspec(dllexport) void MyCppFunc2(ResultOfStrategy* (*allocator)(size_t length))
{
ResultOfStrategy *ros = allocator(size);
for (int i = 0; i < size; i++)
{
ros[i].isGood = i & 1;
ros[i].allProfit = num++;
ros[i].CAGR = num++;
ros[i].DD = num++;
ros[i].countDeals = num++;
ros[i].allProfitF = num++;
ros[i].CAGRF = num++;
ros[i].DDF = num++;
ros[i].countDealsF = num++;
ros[i].ptr = ros + i;
}
size--;
}
}
C#-side:
[StructLayout(LayoutKind.Sequential)]
public struct ResultOfStrategy
{
private byte isGoodInternal;
public bool isGood
{
get => isGoodInternal != 0;
set => isGoodInternal = value ? (byte)1 : (byte)0;
}
public double allProfit;
public double CAGR;
public double DD;
public int countDeals;
public double allProfitF;
public double CAGRF;
public double DDF;
public int countDealsF;
public IntPtr ptr;
}
and then
ResultOfStrategy[] ros;
for (int i = 0; i < 10; i++)
{
using (var pa = new PinnedArray<ResultOfStrategy>())
{
MyCppFunc2(pa.CreateArray);
ros = pa.Array;
// Don't do anything inside of here! We have a
// pinned object here, the .NET GC doesn't like
// to have pinned objects around!
}
for (int j = 0; j < ros.Length; j++)
{
Console.WriteLine($"row {j}: isGood: {ros[j].isGood}, allProfit: {ros[j].allProfit}, CAGR: {ros[j].CAGR}, DD: {ros[j].DD}, countDeals: {ros[j].countDeals}, allProfitF: {ros[j].allProfitF}, CAGRF: {ros[j].CAGRF}, DDF: {ros[j].DDF}, countDealsF: {ros[j].countDealsF}");
}
Console.WriteLine();
}
I'm need rename field "Overlay", but not remove.
I'm try make link native jar lib to xamarin dll.
I`m create new Binding Libriary project and include jar file inside.
But when i'm try build solution, system output window get error
'Overlay': member names cannot be the same as their enclosing type.
I'm try configurate MetaData file in the following way.
<metadata>
<remove-node path="/api/package[#name='Com.Cdcom.Naviapps.Progorod']/class[#name='Overlay']/method[#name='Overlay']" />
</metadata>
or
<remove-node path="/api/package[#name='Com.Cdcom.Naviapps.Progorod']/class[#name='Overlay']" />
or i'm try change EnumMethods
<enum-method-mappings>
<mapping jni-class="/api/package[#name='com.cdcom.naviapps.progorod']/class[#name='Overlay']">
<method jni-name="Overlay" parameter="return" clr-enum-type="Android.OS.Overlay" />
</mapping>
</enum-method-mappings>
but i'm get other error
"generator.exe" exited with code -532462766.
You can see the class from first error below
// Metadata.xml XPath class reference: path="/api/package[#name='com.cdcom.naviapps.progorod']/class[#name='Overlay']"
[global::Android.Runtime.Register ("com/cdcom/naviapps/progorod/Overlay", DoNotGenerateAcw=true)]
public partial class Overlay : global::Java.Lang.Object {
//region "Event implementation for Com.Cdcom.Naviapps.Progorod.Overlay.IOnOverlayListener"
public event EventHandler<global::Com.Cdcom.Naviapps.Progorod.Overlay.OverlayEventArgs> Overlay {
add {
global::Java.Interop.EventHelper.AddEventHandler<global::Com.Cdcom.Naviapps.Progorod.Overlay.IOnOverlayListener, global::Com.Cdcom.Naviapps.Progorod.Overlay.IOnOverlayListenerImplementor>(
ref weak_implementor___SetOnOverlayListener,
__CreateIOnOverlayListenerImplementor,
__v => OnOverlayListener = __v,
__h => __h.Handler += value);
}
remove {
global::Java.Interop.EventHelper.RemoveEventHandler<global::Com.Cdcom.Naviapps.Progorod.Overlay.IOnOverlayListener, global::Com.Cdcom.Naviapps.Progorod.Overlay.IOnOverlayListenerImplementor>(
ref weak_implementor___SetOnOverlayListener,
global::Com.Cdcom.Naviapps.Progorod.Overlay.IOnOverlayListenerImplementor.__IsEmpty,
__v => OnOverlayListener = null,
__h => __h.Handler -= value);
}
}
//endregion
}
Original java code
public class Overlay
{
public List<OverlayItem> getItems()
{
return this.mOverlayItems;
}
public OnOverlayListener getOnOverlayListener()
{
return this.mOverlayListener;
}
public void populate()
{
double[] latlon = new double[this.mOverlayItems.size() * 2];
int d = 0;
for (OverlayItem oi : this.mOverlayItems)
{
latlon[(d++)] = oi.getGeoPoint().getLatitude();
latlon[(d++)] = oi.getGeoPoint().getLongitude();
}
Native.populateOverlay(this.mId, latlon);
}
public void setBitmap(Bitmap bitmap, float xOffset, float yOffset, boolean isPlain, int sizeInMeters)
{
int width = 0;
int height = 0;
int[] pixels = null;
if (bitmap != null)
{
width = bitmap.getWidth();
height = bitmap.getHeight();
pixels = new int[width * height];
bitmap.getPixels(pixels, 0, width, 0, 0, width, height);
}
Native.setOverlayBitmap(this.mId, width, height, pixels, xOffset, yOffset, isPlain, sizeInMeters);
}
public void setOnOverlayListener(OnOverlayListener listener)
{
this.mOverlayListener = listener;
}
public static int SPECIAL_OVERLAY_START_ROUTE = -1;
public static int SPECIAL_OVERLAY_FINISH_ROUTE = -2;
public static int SPECIAL_OVERLAY_ROUTE_SPRITE = -3;
public static int SPECIAL_OVERLAY_GEOBLOG_SPRITE = -4;
private static int SPECIAL_OVERLAYS_COUNT = 4;
public static Overlay specialOverlay(int id)
{
if (mSpecialOverlays[(id + SPECIAL_OVERLAYS_COUNT)] == null)
{
mSpecialOverlays[(id + SPECIAL_OVERLAYS_COUNT)] = new Overlay();
mSpecialOverlays[(id + SPECIAL_OVERLAYS_COUNT)].mId = id;
}
return mSpecialOverlays[(id + SPECIAL_OVERLAYS_COUNT)];
}
protected int getId()
{
return this.mId;
}
protected int mId = mNextId++;
private static int mNextId = 1;
private List<OverlayItem> mOverlayItems = new ArrayList();
private OnOverlayListener mOverlayListener;
private static Overlay[] mSpecialOverlays = new Overlay[SPECIAL_OVERLAYS_COUNT];
public static abstract interface OnOverlayListener
{
public abstract void onOverlayEvent(Overlay paramOverlay, OverlayItem paramOverlayItem);
}
}
I am using a delegate type to call multiple functions from a single point. But when I do like that , I am getting the result somewhat wrong.
public delegate int MyDel(int a,int b);
public static int Add(int a, int b) { return a+b; }
public static int Sub(int a, int b) { return a-b; }
public static void Meth_del()
{
int x,y;
MyDel _delegate;
_delegate = Add;
_delegate += Sub;
Console.WriteLine( _delegate(5,4));
}
Here I should get result 9 and then 1 but only 1 is printed. How ?
This is called Closure. This happens because on the call it will execute both of the subscribed methods and you will be shown with end result.
To avoid such behavior you can unsubscribe (_delegate = null), override subscribers (=) or subscribe (+=) after first call is made.
public static void Meth_del()
{
int x,y;
MyDel _delegate;
_delegate = Add;
// Prints out 9.
Console.WriteLine( _delegate(5,4));
// Override subscribtion.
_delegate = Sub;
// Prints out 1.
Console.WriteLine( _delegate(5,4));
}
Also you can use += to add subscribers to the delegate (as you have written in your question).
Even if both methods are called only the the last method in the delegate will return the result.
Furthermore, you have only one call of Console.WriteLine(), a function cannot receive multiple return values.
To achieve what you want you might have to queue your results like this.
public static Queue<int> Results = new Queue<int>();
public static void Add(int a, int b) { Results.Enqueue(a + b); }
public static void Sub(int a, int b) { Results.Enqueue(a - b); }
public delegate void MyDel(int a, int b);
public static void Meth_del()
{
int x, y;
MyDel _delegate;
_delegate = Add;
_delegate += Sub;
_delegate(5, 4);
while (Results.Any())
Console.WriteLine(Results.Dequeue());
}
Your code does execute both methods but it only displays the return value of the last added method. If you change your methods like this:
public static int Add(int a, int b) {
Console.WriteLine(a+b);
return a+b;
}
public static int Sub(int a, int b) {
Console.WriteLine(a-b);
return a-b;
}
you will see that 9 as well as 1 are written in the console.
Yes its quite obvious that the last method in the delegate will returns the result.
public static void Meth_del()
{
int x,y;
MyDel _delegate;
_delegate = Add;
Console.WriteLine( _delegate(5,4));
_delegate += Sub;
Console.WriteLine( _delegate(5,4));
}
class MultiThreading
{
public class ThreadClass
{
public string InputString { get; private set; }
public int StartPos { get; private set; }
public List<SearchAlgorithm.CandidateStr> completeModels;
public List<SearchAlgorithm.CandidateStr> partialModels;
public ThreadClass(string s, int sPos)
{
InputString = s;
StartPos = sPos;
completeModels = new List<SearchAlgorithm.CandidateStr>();
partialModels = new List<SearchAlgorithm.CandidateStr>();
}
public void Run(int strandID)
{
Thread t = new Thread(() => this._run(strandID));
t.Start();
}
private void _run(int strandID)
{
SearchAlgorithm.SearchInOneDirection(strandID, ref this.completeModels, ref this.partialModels);
}
public static void CombineResult(
List<ThreadClass> tc,
out List<SearchAlgorithm.CandidateStr> combinedCompleteModels,
out List<SearchAlgorithm.CandidateStr> combinedPartialModels)
{
// combine the result
}
}
}
class Program
{
static void Main(string s, int strandID)
{
int lenCutoff = 10000;
if (s.Length > lenCutoff)
{
var threads = new List<MultiThreading.ThreadClass>();
for (int i = 0; i <= s.Length; i += lenCutoff)
{
threads.Add(new MultiThreading.ThreadClass(s.Substring(i, lenCutoff), i));
threads[threads.Count - 1].Run(strandID);
}
**// How can I wait till all thread in threads list to finish?**
}
}
}
My question is how can I wait till all threads in "threads" list to finish and then do the call CombineResult method?
Thanks
You can add a List<Thread> structure to keep record of all your threads
private List<Thread> threads = new List<Thread>();
Then populate the list with the threads
public void Run(int strandID)
{
Thread t = new Thread(() => this._run(strandID));
t.Start();
threads.Add(t);
}
Lastly, you can have a method that calls Join for each thread in a list. It is usually a good practise to have a timeout delay so your program will not block forever (in case there is a bug in a thread)
public void WaitAll(List<Thread> threads, int maxWaitingTime)
{
foreach (var thread in threads)
{
thread.Join(maxWaitingTime); //throws after timeout expires
}
}
An alternative would be to use a Task class and to call Task.WaitAll
Have ThreadClass expose a means to join the thread:
private Thread nativeThread;
public void Run(int strandID)
{
nativeThread = new Thread(() => this._run(strandID));
nativeThread.Start();
}
public void Join()
{
nativeThread.Join();
}
And then use ThreadClass.Join on each thread after you start them:
var threads = new List<ThreadClass>();
for (int i = 0; i <= s.Length; i += lenCutoff)
{
threads.Add(new ThreadClass(s.Substring(i, lenCutoff), i));
threads[threads.Count - 1].Run(strandID);
}
// Waits for each thread to finish in succession
threads.ForEach(t => t.Join());
Or, throw away your ThreadClass and enjoy System.Threading.Tasks:
// e.g. class Models { Complete; Partial; }
// private Models Search(string source, int offset, int length, int ID)
var tasks = new List<Task<Models>>(
from x in Enumerable.Range(0, s.Length / lenCutoff)
select Task.Factory.StartNew<Models>(
() => Search(s, x, lenCutoff, strandID));
);
// private Models CombineResults(IEnumerable<Models> results)
var combine = Task.Factory.ContinueWhenAll<Models>(
tasks.ToArray(),
ts => CombineResults(ts.Select(t => t.Result)));
combine.Wait();
Models combinedModels = combine.Result;
Well I see the answer has already been chosen, but I had already started on writing a solution using System.Threading.Tasks as proposed by several others. Rather than using LINQ, I have tried to match the structure of the original code as much as possible:
class SearchClass
{
public String InputString { get; private set; }
public int StartPos { get; private set; }
public List<string> completeModels;
public List<string> partialModels;
public SearchClass(string s, int sPos)
{
InputString = s;
StartPos = sPos;
completeModels = new List<string>();
partialModels = new List<string>();
}
public void Run(int strandID)
{
// SearchAlgorithm.SearchInOneDirection(...);
}
// public static void CombineResult(...){ };
}
class Program
{
static void Main(string s, int strandID)
{
int lenCutoff = 10000;
if (s.Length > lenCutoff)
{
var searches = new List<SearchClass>();
var tasks = new List<System.Threading.Tasks.Task>();
for (int i = 0; i < s.Length; i += lenCutoff)
{
SearchClass newSearch = new SearchClass(s.Substring(i, lenCutoff), i);
searches.Add(newSearch);
tasks.Add(System.Threading.Tasks.Task.Factory.StartNew(()=>newSearch.Run(strandID)));
}
System.Threading.Tasks.Task.WaitAll(tasks.ToArray());
// Combine the result
}
}
}