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.