How to write extension method which will make invo

2019-09-14 20:44发布

问题:

I have an async delegate which I await in the async method:

async Task M1()
{
    Debug.WriteLine("M1.A");
    await Task.Delay(10);
    Debug.WriteLine("M1.B");
}

async Task M2()
{
    Debug.WriteLine("M2.A");
    await Task.Delay(1);
    Debug.WriteLine("M2.B");
}

delegate Task MyDel();

async void button_Click(object sender, RoutedEventArgs e)
{
    MyDel del = null;
    del += M1;
    del += M2;
    await del();
}

The output is:

M1.A
M2.A
M2.B
M1.B

That is, both invocation members go off simultaneously, not awaiting each other. I need them await each other so the output would be:

M1.A
M1.B
M2.A
M2.B

I tried this in place of await del():

foreach (MyDel member in del.GetInvocationList())
{
    await member();
}

This works. However, I have lots of code places where this needs to be done. Delegates can have different number of parameters of various types but they all return Task.

How can I write an extension method which will let me run the code above by making calls like this?

del0.AwaitOneByOne(); // del0 is 'delegate Task Method()'
del1.AwaitOneByOne(paramInt1, paramStr2); // del1 is 'delegate Task Method(int, string)'
del2.AwaitOneByOne(paramBytes1); // del2 is 'delegate Task Method(byte[])'

回答1:

If you use Func for you delegates rather than custom delegates you could write something like this:

public static class FuncHelper
{
    public static async Task RunSequential<T1>(this Func<T1,Task> del, T1 param1)
    {       
        foreach (var d in del.GetInvocationList().OfType<Func<T1, Task>>())
        {
            await d(param1);
        }
    }

    public static async Task RunSequential<T1, T2>(this Func<T1, T2, Task> del, T1 param1, T2 param2)
    {
        foreach (var d in del.GetInvocationList().OfType<Func<T1, T2, Task>>())
        {
            await d(param1, param2);
        }
    }

// Additional methods for more parameters go here

}

It does seem that you are trying to use delegates in ways they were not really intended. They aren't really supposed to control order or have the functions rely on each other to complete.

Probably better off making a custom collection, maybe override += and -= if you want some delegate like behavior.



回答2:

The cause of your problem is that your delegates have a different set of parameters.

The solution is to create an extra delegate that contains the call inclusive the parameters, similar to the System.Windows.Forms.MethodInvoker delegate.

The only difference in your methodInvoker is that your methodInvoker is not a delegate that returns void, but a delegate that returns a Task.

The function in your extension class would be similar to your foreach:

public delegate task MethodInvoker();

static class DelegateExtensions
{
    public static async Task ExecuteDelegates(this IEnumerable<MethodInvoker> methodInvokers)
    {
        foreach (var methodInvoker in methodInvokers)
        {
            await methodInvoker();
        }
    }
}

Usage would be like:

public MyClass
{
    private async Task F1()
    {
        Debug.WriteLine("Begin F1");
        await Task.Delay(TimeSpan.FromSeconds(1));
        Debug.WriteLine("F1 Completed");
    }

    private async Task F2(TimeSpan waitTime)
    {
        Debug.WriteLine("Begin F2");
        await Task.Delay(waitTime);
        Debug.WriteLine("F2 Completed");
    }

    private async Task F3(int count, TimeSpan waitTime)
    {
         Debug.WriteLine("Begin F3");
        for (int i = 0; i < count; ++i)
        {
            await Task.Delay(waitTime);
        }
        Debug.WriteLine("F3 Completed");
    }
}

public async Task ExecuteMyFunctions()
{
    MyClass X = new MyClass();
    IEnumerable<MethodInvoker> myMethodInvokers = new MethodInvoker[]
    {
        () => X.F1(),
        () => X.F2(TimeSpan.FromSeconds(1)),
        () => X.F3(4, TimeSpan.FromSeconds(0.25)),
    }
    await myMethodInvokers.ExecuteDelegates();
}