0

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[])'
3
  • It is a threading race bug, you cannot fix it by sprinkling magic fairy dust. If your program has a lot of those bugs then you have to rewrite a lot of code. Commented Jan 20, 2017 at 13:57
  • So basically you need to have collection of Tasks which should be run sequentially.. am I got it right? Maybe it's easier to just write class just for that? From my understanding, you use delegate because it provides add/remove and signature check for free. So just as an idea it can be easier to take a look at event and override add / remove to custom method which will chain tasks instead of paralleling them. Of course if you need to "raise event" outside its owner there won't be easy, so you may want to abstract that in some helper class. Commented Jan 20, 2017 at 14:01
  • @Lanorkin Maybe for future projects.. The solution from Mant101 will suffice at this time. Commented Jan 20, 2017 at 14:22

2 Answers 2

2

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.

Sign up to request clarification or add additional context in comments.

Thank you. I understand delegates could not be perfect solution but it's tons of legacy code which already heavily uses them and I just need to fix some bugs. Refactoring it from the ground is not an option at this time.
Sometimes you just have to make the best of the code you have got.
@Manl101 Oops. Now, on await del.RunSequential(1) call I'm getting "'MainPage.MyDel' does not contain a definition for 'RunSequential' and the best extension method overload 'FuncHelper.RunSequential<int> (Func<int, Task>, int)' requires a receiver of type 'Func<int, Task>'". I modified my delegate declaration and methods to include an int parameter: delegate Task MyDel(int i), async Task M1(int i), async Task M2(int i).
If you aren't using Func you will need to change it to your custom delegate, unfortunately if you have multiple custom delegates with the same number of params you will need multiple methods. If you are stuck with legacy code and refactoring to Func isn't viable that might be the best you can do.
Two different delegates with the same signature are treated as completely different in C# and you can't just use them interchangeably. Delegates are really objects with some syntactic sugar to make initializing and using them nicer, but MyDel doesn't inherent from Func<int, Task> so you can't use one in place of the other.
-1

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

Does it mean I'll need to change all the declaration of these delegates? For me, it looks like your solution provides a completely different way of doing things rather than providing 'await' way of calling already assigned delegates.

Your Answer

Draft saved
Draft discarded

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.