1

Situation

In client/server architectures it is common to deactivate UI controls during long running operations, such as server side communication. This answer shows a dedicated solution to a very specific situation.

// condensed code listing from linked answer
private async void Button_Click_1(object sender, RoutedEventArgs e)
{
    DisableUI(); 
    try { await ViewModel.CreateMessageCommand(); }
    finally { EnableUI(); }
}

Is there a way to generalise/abstract this kind of operation and reuse it? This code tends to be repeated many times in some applications. However it follows the best practices pointed out Stephen Cleary's articles, like to use async all the way and to prefer async Task over async void except for event handling, e.g. this button click handler. Async Await Best Practices

Assume to have the following portion of potentially long running code:

// somewhere in an appropriate scope
async Task TheWorkWeGotToDo() { Task.Delay(2000); }

Some attempts were tested to centralise the wrapping of the UI enabling and disabling code around this potentially long running operation:

Attemp 1

class OurButton extends SomeKnownButton {
  Action theWork;
  public async void HandleClick(object sender, EventArgs args) {
    DisableUI();
    try { await theWork(); }
    finally { EnableUI(); }
}

This attempt doesn't allow to await the declared action and throws a compile error, because of misusing the non async Action instance.

Attempt 2

class OurButton extends SomeKnownButton {
  Task theWork;
  public async void HandleClick(object sender, EventArgs args) {
    DisableUI();
    try { await theWork; }
    finally { EnableUI(); }
}

This attempt allows to await the work specified as a Task and allows to separate the creation of the Task from its execution as pointed out on the async await beginners from Microsoft. Async-Await Beginners Article from Microsoft

// Signature specifies Task<TResult>
async Task<int> TaskOfTResult_MethodAsync()
{
    int hours;
    // . . .
    // Return statement specifies an integer result.
    return hours;
}

// Calls to TaskOfTResult_MethodAsync
Task<int> returnedTaskTResult = TaskOfTResult_MethodAsync();
int intResult = await returnedTaskTResult;
// or, in a single statement
int intResult = await TaskOfTResult_MethodAsync();

This compiles, but makes it difficult to define the required work which should be wrapped. Some unsatisfying attempts for this situation were:

Attempt 2a

OurButton b = /* init */;
b.theWork = TheWorkWeGotToDo; // compile error

Attempt 2b

OurButton b = /* init */;
b.theWork = new Task(() => TheWorkWeGotToDo());

This compiles, however it doesn't behave as expected.

Attempt 3

Another possibility is to declare a virtual member in the button class. Subclasses can implement this operation using the async keyword.

class T {
    async void Test() { await AsyncWork(); }  
    public virtual async Task AsyncWork() {} // warning: will run synchronously
}

class WrappedOperation : T {
  public override async Task AsyncWork() { await TheWorkWeGotToDo(); }
}

Deriving for every single situation from the button class seems very inappropriate.

Question

Speaking with some functional and aspect oriented programming background: Is it somehow possible to decorate a function to asynchronously intercept a portion of code?

OurButton b = /* init */;
Action someWork = /* some long running task */;
Action guardedWork = someWork.SurroundWithUiGuards(b);
b.Clicked += guardedWork;

// in some appropriate scope
static class Extension {
  public static Action SurroundWithUiGuards(this Action work, SomeKnownButton b) {
    return () => {
      b.Disable();
      try { work(); }
      finally { b.Enable(); }
    };
  }
}

So in this case: Is it somehow possible to take an Action like someWork and wrap around interception code which guards the ui elements and returns a new 'Action' instance?

Notes

This is a somewhat open question and solution to the specific problem suffices. However, any suggestions answering the abstract question are really appreciated! The current target environment is the single threaded ui framework, Xamarin.

I think the main problem is that it doesn't seem to be possible to declare async actions as fields on the one hand. On the other hand it is possible to declare async lambda expressions, but how can these be awaited? I also hope that i am overseeing something very obvious.

I'm really new to c# async await programming, but i already read plenty of async and await articles.

Community
  • 1
  • 1
isaias-b
  • 2,255
  • 2
  • 25
  • 38
  • Read [these articles](https://docs.com/paulo-morgado/8860/async-await-general) to know how `async-await` works. – Paulo Morgado Aug 26 '15 at 00:39
  • @PauloMorgado Thanks for your advice, but could you be a little more precise? I already read many articles but i am still sometimes confused with the whole subject. Some keywords would help. – isaias-b Aug 26 '15 at 10:20
  • I edited my question and also added a now working answer to my question. If nobody complaints about obvious errors i would accept my own answer during the day. – isaias-b Aug 26 '15 at 10:21

1 Answers1

2

A solution that actually worked out is to define everything using Task objects. Tasks can be awaited and thus point out a solution to wrap a long running operation with interception code:

static class Ext {
  public static async Task SurroundWithUIGuard(this Task task, SomeButton button) {
    button.Enabled = false;
    try { await task; }
    finally { button.Enabled = true; }
  }
}

This is the functional way to separate the task creation from its execution and to wrap around code before and after it by modifying the Task instance. To define an event handler one only needs to throw in some more async and await expressions.

SomeButton b = /* init */;
b.Clicked += async (s,e) => await Task.Delay(5000).SurroundWithUIGuard(b);

Furthermore the async lambda expression is the only async void in this series of async Task operations and thus follows the best practices. It still seems a bit verbose because of the heavy use of the async await keywords, but allows to reuse this portion of code.

isaias-b
  • 2,255
  • 2
  • 25
  • 38