Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Pattern for an async method that returns something immediately

If I write a simple function I can get a result immediately. If I use async/await and return a Task - the method will kinda return when it's done with the task, but what if I need to write a method that needs to return immediately, but then continue updating the result and potentially eventually complete the task? Also what if I want to expose it outside a WinRT Component library for consumption in components in other languages? How would I do it in C# and how would I do it in C++/CX? Or JS perhaps?

Example 1:

I want to expose a property that returns an ImageSource so I can immediately bind it from my MVVM view model to the XAML view. The method to load the ImageSource is going to be in a separate class that is exposed outside a WinRT Component (it is a public method). Now I want that method to be awaitable, or at least somehow return a task I can await but also immediately return the ImageSource so the property I call it from can immediately return since properties can't be async. The caller doesn't know what type the ImageSource will be so it cannot instantiate it since ImageSource is virtually an abstract type and is typically represented as a BitmapImage or WriteableBitmap and in my case both can be returned from the method. Obviously the method itself immediately knows whether it is going to return either of the types of objects, but it takes some time to read/create and or decode the image.

I'm thinking the signature might be something like this in C#

public async Task<ImageSource> GetImage(
    object key,
    out ImageSource bitmap,
    CancellationToken cancellationToken)

and I would simply not await the result of the method in the property accessor, but I'm thinking I'll be able to return the bitmap argument immediately, while when called elsewhere or event elsewhere in the code of my view model I'll be able to await or cancel the task.

Example 2:

I want to be able to list files from a disk and get a task that is complete when all files have been listed, but immediately return an IObservableVector of view models representing the files for use in my XAML UI that updates as pages of the files are loaded asynchronously.

Here I would probably do something similar:

public async Task<int> GetImages(
    object queryParemeters,
    out ObservableCollection<CustomFileInfoType> files,
    CancellationToken cancellationToken)

Problems

Now the above look almost good, but I don't think I can expose a TPL Task outside a WinRT Component since Task is not a WinRT type, so I'd probably gave an internal method like the above and a public one that wraps the result as an IAsyncOperation by calling AsyncInfo.Run(), passing the task and cancellation token. ObservableCollection is also .NET only, so I'd probably need to create a wrapper around it that implements an IObservableVector since I don't think one is available in .NET. There are probably other potential problems with those and I am not sure if this design is right.

Then also - how would I do all this in C++/CX? Or JS?

like image 705
Filip Skakun Avatar asked Mar 20 '23 19:03

Filip Skakun


1 Answers

async is built on the notion of an asynchronous operation, with a definite beginning and ending. At the end, there may be a single result. That's it. Note that async methods may not have out parameters because they don't fit into this model.

If you want a stream of values, then use Reactive Extensions. There's an interesting RxUI library that nicely marries observables with MVVM patterns.

That said, I don't think either of your examples actually require observables (though you certainly could move to Rx if you wanted). I address your first example (data-bound async properties) on my blog; the short answer is to use a wrapper for Task<T> that implements INotifyPropertyChanged like this one:

// Service
public async Task<ImageSource> GetImage(object key, CancellationToken cancellationToken);

// ViewModel
INotifyTaskCompletion<ImageSource> Image { get; private set; }
...
Image = NotifyTaskCompletion.Create(GetImage(key, token));

// View
<Image Source="{Binding Image.Result}" />

Regarding your second example, this can be done fairly easily by treating the new items as progress updates from the async method:

// Service
public async Task<int> GetImages(object queryParemeters,
    CancellationToken cancellationToken,
    IProgress<CustomFileInfoType> progress);

// ViewModel
var collection = new ObservableCollection<CustomFileInfoType>();
var progress = new Progress<CustomFileInfoType>(x => collection.Add(x));
await GetImages(query, token, progress);

Exposing these types is something else completely. WinRT components must expose WinRT types. I recommend you write the basic logic (service and possibly ViewModel) using pure async/await and then do the translation separately. As you noted, AsyncInfo.Run will translate Task to IAsyncOperation, and there isn't a built-in translator for ObservableCollection to IObservableVector (though it isn't hard to write, and there are several available via Google).

Then also - how would I do all this in C++/CX? Or JS?

I have no idea on that one. You'll probably have to write your own equivalent for NotifyTaskCompletion on those platforms, or just use callbacks.

like image 140
Stephen Cleary Avatar answered Apr 26 '23 02:04

Stephen Cleary