【问题标题】:Passing method to be executed asynch传递异步执行的方法
【发布时间】:2014-02-05 06:34:30
【问题描述】:

我正在创建一个 Windows 窗体应用程序,它对一条数据执行许多类似的操作。所有这些操作几乎相同,所以我想创建一个单独的方法,

  • 将用户界面设为只读
  • 设置诸如“Executing operation1...”之类的消息
  • 异步执行操作,因此用户界面保持响应
  • 将消息设置为“Operation1 已完成”。
  • 将接口只读设置为 false。

我想像这样调用这个单一方法

ExecuteOperation([method to execute], "Executing operation1")

我确信这很简单,但我在委托和任务之间迷失了方向,所以请告诉我如何编写一个能够运行选定方法的方法,最好使用多个参数以及如何调用此方法。

注意:

我的意思是禁用界面

MainMenu.Enabled = false;
txtData.ReadOnly = true;

【问题讨论】:

  • ExecuteOperation(Action act, string msg)
  • 将 UI 设为只读是什么意思?禁用 form.Enabled = false 之类的所有内容?
  • 我需要一个接受多个参数的方法。动作不接受任何参数...
  • Action 怎么样? msdn.microsoft.com/en-us/library/018hxwa8(v=vs.110).aspx(有最多需要 16 个参数的重载——如果你真的想这样做的话!)

标签: c# .net winforms asynchronous async-await


【解决方案1】:

您可能正在寻找类似的东西(我在这里展示了一个带有参数的方法的示例,因为您专门询问了这种情况):

    private async Task ExecuteOperation(string operationName, Action action)
    {
        //Disable UI here
        //Set 'Executing' message here
        await Task.Run(action);
        //Set 'Finished' message here
        //Enable UI here
    }

    private async Task CallOperation()
    {
        int x, y, z; //These get set to something here...
        await ExecuteOperation("Operation1", () => OperationWithParams(x, y, z));
    }

如果您的各种操作可以抛出标准异常并且应该导致某种 UI 反馈,那么您很可能还希望在 ExecuteOperation 包装器中添加一些异常处理。

在旁注中,我重新排序了名称和操作,因为这使得传递匿名方法作为操作更加简洁 (IMO)。

【讨论】:

  • 谢谢,很优雅。 () => 部分是什么意思?
  • @Germstorm,该语法是lambda expression,在这种情况下编译为匿名方法。因为匿名方法没有参数(声明的() 部分)并且没有返回值,所以它与Action 委托类型兼容。在这种特殊情况下,我们还利用称为closures 的很酷的小技巧来捕获要在此匿名方法中使用的局部变量的值。
  • 在旁注中,编译器在后台做了很多荒谬的事情,以允许上面的简洁语法。生成了几个帮助类和许多精心设计的魔法,以允许异步恢复的代码像一系列简单的操作一样干净地流动和传播异常。如果你有时间,值得更多地了解它在幕后所做的事情,因为它真的非常棒。
  • 非常感谢@Dan
【解决方案2】:

您可以将方法传递给方法,可以这么说,但委托必须匹配。如果您有具有不同签名的方法,那么您的角度是错误的。

Action 和 Func 委托是通用的。有一个可以匹配几乎任何签名(最多 16 个参数),但同样,它们必须匹配方法签名。如果你有一个方法有一个参数,另一个有两个,或者两个有不同类型的参数,你不能将它们传递给同一个委托。

所以,这实际上取决于您的方法有多相似。如果它们具有不同类型的参数,您可能必须“包装”这些调用。

简化示例:

void Main()
{
    List<Action> actions = new List<Action>
    {
        () => Method1("myString"),
        () => Method2("myString2", "myString3")
    };
    foreach(var action in actions) InvokeAction(action);
}

void InvokeAction(Action action)
{
    action();
}

void Method1(string x)
{
    Console.WriteLine(x);
}

void Method2(string y, string z)
{
    Console.WriteLine(y);
    Console.WriteLine(z);
}

另一方面,如果你的方法具有相同的签名,那就更简单了:

void Main()
{
    InvokeAction(Method1, "myString");
    InvokeAction(Method2, "myString2");
}

void InvokeAction(Action<string> action, string param)
{
    action(param);
}

void Method1(string x)
{
    Console.WriteLine(x);
}

void Method2(string y)
{
    Console.WriteLine(y);
}

现在,关于异步运行,如果您使用的是 .NET 4.0,它就像使用 System.Threading.Task 一样简单。您可以像这样更改我的示例方法:

void InvokeAction(Action<string> action, string param)
{
    Task.Factory.StartNew(() => action(param));
}

【讨论】:

    【解决方案3】:

    这样的事情怎么样(非 async/await - 版本):

    void Foo<T>(Action<T> action, string message)
    {
      MethodWhichMakesMyInterfaceReadOnlyAndSetsMessage(message);
      BackgroundWorker worker = new BackgroundWorker();
      worker.DoWork += (obj, arg) => action.Invoke();
      worker.RunWorkerCompleted += 
           (obj, arg) => 
            {
               MethodWhichMakesMyInterfaceReadWrite();
            };
    
      worker.RunWorkerAsync();
    }
    

    在我意识到你特别想要 async/await 和任务之前,我写了这个 - 但是其他人已经回答了这个问题。您可以为操作的额外参数创建任何您想要的重载。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2019-03-22
      • 2020-11-13
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-04-02
      相关资源
      最近更新 更多