如何编写扩展方法,使多播C#委托中的调用列表成员按顺序运行?

如何编写扩展方法,使多播C#委托中的调用列表成员按顺序运行?,c#,delegates,async-await,extension-methods,C#,Delegates,Async Await,Extension Methods,我在异步方法中等待一个异步委托: 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 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();
}
输出为:

M1.A
M2.A
M2.B
M1.B
也就是说,两个调用成员同时启动,而不是互相等待。我需要他们互相等待,以便输出:

M1.A
M1.B
M2.A
M2.B
我试着用这个代替
等待del()

这很有效。然而,我有很多代码地方需要这样做。代理可以有不同数量的各种类型的参数,但它们都返回
任务

如何编写一个扩展方法,让我通过这样的调用来运行上面的代码

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[])'

如果对委托使用Func而不是自定义委托,则可以编写如下内容:

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

}
公共静态类
{
公共静态异步任务RunSequential(此函数del,T1参数1)
{       
foreach(类型()的del.GetInvocationList()中的变量d)
{
等待d(参数1);
}
}
公共静态异步任务RunSequential(此函数del、T1参数1、T2参数2)
{
foreach(类型()的del.GetInvocationList()中的变量d)
{
等待d(参数1,参数2);
}
}
//更多参数的其他方法请参见此处
}
看起来您确实在试图以非预期的方式使用代理。他们不应该真正控制秩序,也不应该让功能相互依赖来完成


如果您想要一些类似委托的行为,最好创建一个自定义集合,或者重写+=和-=

问题的原因是您的代理有一组不同的参数

解决方案是创建一个额外的委托,该委托包含包含参数的调用,类似于

methodInvoker的唯一区别在于,methodInvoker不是返回void的委托,而是返回任务的委托

扩展类中的函数类似于foreach:

public delegate task MethodInvoker();

static class DelegateExtensions
{
    public static async Task ExecuteDelegates(this IEnumerable<MethodInvoker> methodInvokers)
    {
        foreach (var methodInvoker in methodInvokers)
        {
            await methodInvoker();
        }
    }
}
公共委托任务方法调用器();
静态类委派扩展
{
公共静态异步任务ExecuteDelegates(此IEnumerable methodInvokers)
{
foreach(methodInvokers中的var methodInvoker)
{
等待methodInvoker();
}
}
}
用法如下:

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();
}
公共MyClass
{
专用异步任务F1()
{
Debug.WriteLine(“beginf1”);
等待任务延迟(时间跨度从秒(1));
Debug.WriteLine(“F1已完成”);
}
专用异步任务F2(TimeSpan waitTime)
{
Debug.WriteLine(“开始F2”);
等待任务。延迟(等待时间);
Debug.WriteLine(“F2已完成”);
}
专用异步任务F3(整数计数,TimeSpan waitTime)
{
Debug.WriteLine(“beginf3”);
对于(int i=0;iX.F1(),
()=>X.F2(从秒开始的时间跨度(1)),
()=>X.F3(4,时间跨度从秒(0.25)),
}
等待myMethodInvokers.ExecuteDelegates();
}

这是一个线程竞赛错误,你无法通过喷洒魔法仙尘来修复它。如果你的程序有很多这样的bug,那么你必须重写很多代码。因此,基本上你需要有一组任务,这些任务应该按顺序运行。。我说得对吗?也许仅仅为此编写类更容易些?据我所知,您使用
委托
,因为它免费提供添加/删除和签名检查。因此,作为一种想法,可以更容易地查看
事件
并重写add/remove to custom方法,该方法将链接任务而不是并行任务。当然,如果您需要在事件所有者之外“引发事件”,这并不容易,因此您可能希望在某个助手类中抽象该事件。@Lanorkin可能用于将来的项目。。Mant101的解决方案现在就足够了。谢谢。我知道委托不可能是完美的解决方案,但它是大量使用它们的遗留代码,我只需要修复一些bug。此时,从根本上重构它不是一个选项。有时你只需要充分利用你所拥有的代码。@Manl101 Oops。现在,在
wait del.RunSequential(1)
call I'm get“'MainPage.MyDel'不包含'RunSequential'的定义,并且最佳扩展方法重载'FuncHelper.RunSequential(Func,int)'需要'Func'类型的接收器。我修改了我的委托声明和方法以包含一个int参数:
delegate Task MyDel(int I)
async Task M1(int I)
async Task M2(int I)
。如果不使用Func,则需要将其更改为自定义委托,不幸的是,如果有多个自定义委托具有相同数量的参数,则需要多个方法。如果你被遗留代码所困扰,而对Func进行重构是不可行的,那么这可能是你能做的最好的事情。在C#中,两个具有相同签名的不同委托被视为完全不同的,你不能互换使用它们。委托实际上是具有一些语法糖分的对象,可以更好地初始化和使用它们,但MyDel不是Func固有的,因此不能使用一个来代替另一个。这是否意味着我需要更改这些委托的所有声明?对我来说,您的解决方案似乎提供了一种完全不同的做事方式,而不是提供“等待”方式来调用已分配的代理。
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();
}