Task-based async best practice

This forum is meant for anything you would like to share with other visitors
NickFriend
Posts: 248
Joined: Fri Oct 14, 2016 7:09 am

Task-based async best practice

Post by NickFriend »

This is a bit off topic (and rather long), but I think it could be a useful discussion - certainly for me ;) The pseudo code I'm going to use is based on C#, but everything in the discussion applies equally to X#.

We have a largish WPF MVVM app which gets its data from web-servers via WCF services. To keep everything dynamic, all calls to the web services are asynchronous using async-await and Tasks. Although the app is generally very stable with hundreds of users across many dozens of installations, we do get occasional but persistent unhandled exceptions which are irritating, so I'm trying to spot potential weak spots in the code.

One possible issue is the following. We have a consistent pattern for data retrieval in our ViewModels (in C# pseudo code):

Code: Select all

// this is called from a RelayCommand when the user presses the search button
private void SearchForData()
{
    this.FetchData();
}

private async void FetchData()
{
    // this calls a method using Task.Factory.StartNew and returns Task<T>
    List<T> mydatalist = await _dataservice.FetchDataFromWCFServiceAsync() 
    // MyDataBoundCollection is a property bound to a grid in the View
    MyDataBoundCollection = new ObservableCollection<T>(mydatalist);
}
In reality it's a bit more complex.... the awaited method actually returns a result object that encapsulates any errors that occur in the dataservice method and which is checked before continuing, and there may be some other pre-processing of the returned data, but this is the basics. The important thing is that this is fire and forget - a status message is displayed and the UI remains responsive, as all that happens is when the dataservice method completes it updates the ObservableCollection and databinding means this will then display on screen.

However note the method signature - async void. Researching I find this is a big no-no as async void methods can't handle normal try-catch error handling, and any error in the method will escalate up as an unhandled exception. After a fair amount of research I've come up with the following refactoring:

Code: Select all

private void SearchForData()
{
    Task.Run(async ()=> await this.FetchDataAsync());
}

private async Task FetchDataAsync()
{
    // now I can use try-catch as normal
    try
    {
        List<T> mydatalist = await _dataservice.FetchDataFromWCFServiceAsync() 
        MyDataBoundCollection = new ObservableCollection<T>(mydatalist);
    }
    catch
    {
    }
}
Does anyone have any experience with this or have any feedback on this issue. I've found a lot on the principle of "async all the way up", but this doesn't quite apply as this situation is a fire-and-forget scenario which relies on databinding to complete.

Any thoughts or feedback gratefully accepted.

Nick
Last edited by ic2 on Fri Jun 22, 2018 3:17 pm, edited 1 time in total.
TerryB1
Posts: 306
Joined: Wed Jan 03, 2018 11:58 am

Task-based async best practice

Post by TerryB1 »

Hi Nick

No experience with this whatsoever, but if a try/catch gives problems could you not use some sort of if/then/else construct?

Terry
TerryB1
Posts: 306
Joined: Wed Jan 03, 2018 11:58 am

Task-based async best practice

Post by TerryB1 »

Hi Nick

My last response to your post "async best practice" was my immediate reaction to what I inferred you were trying to achieve.

Having given more thought to the problem, I a inclined to the view that, although some may regard it as a fudge work-around, it is indeed a reasonable approach.

However, if it is to be regarded as good practice, some justification for this view is needed.

My thinking goes along the following lines:

The try/catch constructs are designed to enable escalation of deeply embedded exceptions to escalate upwards to more generic "catch code".

When using await constructs you are affecting timing which is intrinsic in the operation of any application; either relative timing or absolute timings. It follows that the conditions for any generic catch-coding to have any integrity would be almost impossible to code. At the very least they would be application specific and convoluted.

Thus, try/catches around anything that affects timing are best avoided.

This is exactly what an if/then/else construct does. It makes the logic employed application specific.

I think that's effectively what you've done with your "private async Task FetchDataAsync()" method.


Let me now put things a different way:

There are two and only two coding-routes through any program. Deterministic and non-deterministic. A deterministic route will end satisfactorily and result in some specific output. A non-deterministic output, or more accurately a continuing sequence of non-deterministic coding, will end ultimately with an infinite number of options to consider: highly undesirable not to say totally impractical!

Fortunately we can interrupt a sequence of non-deterministic code to reduce ensuing options until we get a coding sequence which is totally deterministic. This requires "human" decision.

There are only two humans involved- the developer and the user. So it is always one of these persons who ust decide.

In the case of program development it is, of course the developer. Selecting a programming language is the first way of reducing coding options, followed by the dictates of the language syntax.

In the case of programming, say, a game of chess, it is the user or opponent who make the choice.

I hope the above makes some sense.


Best Regards

Terry
TerryB1
Posts: 306
Joined: Wed Jan 03, 2018 11:58 am

Task-based async best practice

Post by TerryB1 »

And finally have you considered the C# 7 ValueTask type ?

You may need to install System.Threading.Tasks.Extensions from NuGet.

Terry
NickFriend
Posts: 248
Joined: Fri Oct 14, 2016 7:09 am

Task-based async best practice

Post by NickFriend »

Hi Terry,

Thanks for the input... I have to investigate ValueTask, it wasn't on my radar.

I'm actually approaching this from a different angle, the async side rather than the try-catch. For me the essential issue is the following...

First, the method signature async void is to be avoided (no pun intended!) because of the way it negates the use of try-catch, leaving the code vulnerable to run-time errors. The solution is to change to async Task, whch is effectively a void Task - this then allows correct use of try-catch which solves the potential errors problem.

However because we're now returning a Task, we start to propagate async calls up the code tree to the calling method. The general advice when using asynchronous coding is that it should be async "all the way up", so this then keeps pushing the async calls upwards, otherwise we just end up blocking the UI at some other point.

However this concept of async all the way up is based on the code awaiting the result of the lowest async call - typically some data that's being retrieved and that the calling code requires to be able to continue.

But with WPF, MVVM and databinding you can suddenly cut across all of that, because we don't need code saying "assign this List of returned data to this control on the window" (as Dick might prefer to do in code-behind for example), we just assign an INotifyable property to the ItemsSource of our grid control and databinding will update it when the property is updated. So my awaited call to get data simply updates the property when it completes and that's it.

So the calling method that invokes the async Task method doesn't actually need to await it, because it doesn't need the result of the awaited method. It is truly "fire and forget".

So in this context propagating the async calls upwards is an unnecessary complication. My Task.Run code is a way of trying to circumvent this, but to be honest I'm not 100% sure that it's either a correct or valid solution :S

All the best

Nick
FFF
Posts: 1581
Joined: Fri Sep 25, 2015 4:52 pm
Location: Germany

Task-based async best practice

Post by FFF »

Nick,
we just assign an INotifyable property to the ItemsSource of our grid control and databinding will update it when the property is updated.
well, who is "it"? Someone has to listen for the update, something might "need" the actual content to proceed. Not sure, if there are no dragons in the dark...
Regards
Karl
(on Win8.1/64, Xide32 2.20, X#2.20.0.3)
NickFriend
Posts: 248
Joined: Fri Oct 14, 2016 7:09 am

Task-based async best practice

Post by NickFriend »

Fear of dragons is why I want the try-catch to work in this context!

Nick
User avatar
lumberjack
Posts: 727
Joined: Fri Sep 25, 2015 3:11 pm
Location: South Africa

Task-based async best practice

Post by lumberjack »

Hi Nick,
NickFriend wrote:
Fear of dragons is why I want the try-catch to work in this context!
I suggest you watch the movie:How to train your dragon... :lol:
______________________
Johan Nel
Boshof, South Africa
TerryB1
Posts: 306
Joined: Wed Jan 03, 2018 11:58 am

Task-based async best practice

Post by TerryB1 »

Hi Nick

Thanks - Understood: a different cut of the operating matrix.

As for dragons, I think most have now been slain

But two remain: Logical accuracy and unnecessary or redundant code, both of which manifest themselves when the application is run.

The first may prove disastrous, the second at worst may unnecessarily reduce performance.

In general terms determination of Logical accuracy depends on the complexity of that logic. Thus, rather than a dragon, it becomes a "wolf in sheep's clothing."

The logical accuracy of your suggested code is irrefutable.

As for redundancies in operation, I don't see any.

I hope this serves to put some of your concerns to bed.

Best Regards

Terry
ic2
Posts: 1858
Joined: Sun Feb 28, 2016 11:30 pm
Location: Holland

Task-based async best practice

Post by ic2 »

Hello Nick,

Catching up I read this post and as about all I use on async I learned from you on the Devshare conferences it is also interesting for me. I assume your original design criteria are based on this article?

https://msdn.microsoft.com/en-us/magazine/jj991977.aspx

I was wondering if you read this:

https://www.infoworld.com/article/31498 ... mming.html

There's a big red cross next to the Task.Run(()=> way you use too.

In general I often find that creating something "by the book" does improve the working of the code (our WPF system is working very well) but if something in it doesn't work it's usually more difficult to find why not. I agree with you that effectively redesigning your methods to go "all the way up" is not an appealing idea...

Dick
Post Reply