0

I am creating a Windows forms application which executes a lot of similar operations on a piece of data. All these operations are almost the same so I would like to create a single method that

  • makes the user interface read-only
  • sets up messages like "Executing operation1..."
  • executes the operation asynchronously, so the user interface remains responsive
  • sets up message to "Operation1 finished."
  • Set interface read-only to false.

I imagine calling this single method like

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

I am sure that this is simple but I am lost between delegates and tasks so please show me how to write a method that is capable of running a selected method, preferably with multiple parameters and how this method is called.

Note:

By disabling the interface I mean

MainMenu.Enabled = false;
txtData.ReadOnly = true;
Germstorm
  • 9,709
  • 14
  • 67
  • 83
  • 1
    `ExecuteOperation(Action act, string msg)` – L.B Feb 04 '14 at 22:12
  • What do you mean by making the UI read-only? Disabling everything like `form.Enabled = false`? – noseratio Feb 04 '14 at 22:16
  • I need a method that accepts multiple parameters. Action does not accept any parameters... – Germstorm Feb 04 '14 at 22:19
  • 3
    What about Action? http://msdn.microsoft.com/en-us/library/018hxwa8(v=vs.110).aspx (There are overloads which take up to 16 params - if you really want to do that!) – Jay Feb 04 '14 at 22:20

3 Answers3

3

You're probably looking for something like this (I've shown an example here for a method that takes parameters, since you asked for that case specifically):

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

You most likely also want to add some exception handling in your ExecuteOperation wrapper if you have standard exceptions that your various operations can throw and which should result in UI feedback of some sort.

On a side note, I reordered the name and the action, as this makes it somewhat cleaner (IMO) to pass an anonymous method as the action.

Dan Bryant
  • 27,329
  • 4
  • 56
  • 102
  • Thank you, it is elegant. What does the () => part mean? – Germstorm Feb 05 '14 at 15:10
  • 1
    @Germstorm, that syntax is a [lambda expression](http://msdn.microsoft.com/en-us/library/bb397687.aspx), which compiles in this case down to an anonymous method. Because the anonymous method takes no arguments (the `()` portion of the declaration) and has no return value, it is compatible with the Action delegate type. In this particular case, we're also capturing the value of local variables to use in this anonymous method, taking advantage of a cool little trick known as [closures](http://stackoverflow.com/questions/9591476/are-lambda-expressions-in-c-sharp-closures). – Dan Bryant Feb 05 '14 at 17:52
  • 1
    On a side note, the compiler is doing a ridiculous amount of stuff in the background to allow for the concise syntax above. There are several helper classes generated and a lot of carefully crafted magic to allow for asynchronously resumed code to flow and propagate exceptions cleanly as if were a simple series of operations. If you get the time, it's well worth learning more about what it's doing under the hood, as it's really quite awesome. – Dan Bryant Feb 05 '14 at 18:00
1

You can pass methods into methods, so to speak, but the delegates have to match. If you have methods with different signatures, you're angling the wrong way.

The Action and Func delegates are generic. There's one to match almost any signature (up to 16 parameters), but again, they have to match the method signature. If you have one method with one parameter, and another with two, or two with different types of parameters, you can't pass those into the same delegate.

So, really it depends on how similar your methods are. If they have varied types of parameters, you'd probably have to "wrap" these calls.

Simplified example:

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

On the other hand, if your methods have the same signature, it's a bit simpler:

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

Now, as to running that asynchronously, it's as simple as using a System.Threading.Task, if you're on .NET 4.0. You could alter my example method as such:

void InvokeAction(Action<string> action, string param)
{
    Task.Factory.StartNew(() => action(param));
}
Curtis Rutland
  • 776
  • 4
  • 12
0

How about something like this (non async/await - version):

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

I wrote this before I realised you wanted async/await and tasks specifically - someone else has answered with that however. You can create whatever overloads you want for the extra params for your action.

Jay
  • 9,561
  • 7
  • 51
  • 72