Cancelling a Windows Runtime asynchronous operation, part 1: C#

Raymond Chen

Raymond

The Windows Runtime has a lot of asynchronous operations, and the typical way of dealing with them is to schedule a continuation when the operation completes. Depending on the language, it might be Promise.then or task.then() or await or co_await. They all boil down to “Return from the function immediately, and resume execution when this asynchronous thing produces a value.”

Windows Runtime asynchronous operations can be cancelled, and then things get interesting.

Invoking the Cancel method is, technically, merely a request and not a demand. Operations are advised to support cancellation, but it is not strictly required. An operation might choose to ignore your cancellation request outright. Or it could process the cancellation request by causing the operation to complete immediately with partial results.

But let’s assume that the operation accepted your cancel request and indeed completed the operation with a status of Canceled.

What does your code see as the result of the cancellation?

Let’s start with C#. In order to cancel the operation, you have to wrap it in a task, and then cancel the task.

var picker = new FileOpenPicker { FileTypeFilter = { ".txt" } };
var cts = new CancellationTokenSource();
cts.CancelAfter(TimeSpan.FromSeconds(3));

StorageFile file;
try {
    file = await picker.PickSingleFileAsync().AsTask(cts.Token);
} catch (TaskCanceledException) {
    file = null;
}

if (file != null) {
    DoSomething(file);
}

We cancel the file picker dialog after three seconds. This is done by taking the IAsyncOperation returned by Pick­Single­File­Async(), convert it to a Task with AsTask, and associate it with a cancellation token source that has been configured to cancel after three seconds.

When this operation is canceled, you get a Task­Canceled­Exception. This was the result of the Windows Runtime asynchronous operation completing with a status of Canceled. The C# projection then converts this to a Task­Canceled­Exception.

This is the behavior that C# asynchronous code expects, so it’s natural that the C# projection of Windows Runtime asynchronous operations into tasks behaves this way.

Next time, we’ll look at C++/CX with PPL.

Bonus chatter: You can also cancel the task by talking directly to the IAsyncOperation instead of converting it into a C# Task.

async void CancelAfter(IAsyncInfo info, TimeSpan delay)
{
    await Task.delay(delay);
    info.Cancel();
}

var picker = new FileOpenPicker { FileTypeFilter = { ".txt" } };
StorageFile file;
try {
    var op = picker.PickSingleFileAsync();
    CancelAfter(op, TimeSpan.FromSeconds(3));
    file = await op;
} catch (TaskCanceledException) {
    file = null;
}

if (file != null) {
    DoSomething(file);
}

You could try to earn some style points by moving the CancelAfter code inline.

var picker = new FileOpenPicker { FileTypeFilter = { ".txt" } };
StorageFile file;
try {
    var op = picker.PickSingleFileAsync();
    ((Action)(async () => { await Task.Delay(TimeSpan.FromSeconds(3)); op.Cancel(); }))();
    file = await op;
} catch (TaskCanceledException) {
    file = null;
}

Or perhaps more usefully, let CancelAfter return the original asynchronous operation, so you can cancel it and await it at one go.

public static class Helpers
{
    static async void CancelAfterHelper(IAsyncInfo info, TimeSpan delay)
    {
        await Task.Delay(delay);
        info.Cancel();
    }

    static public IAsyncAction CancelAfter(this IAsyncAction action, TimeSpan delay)
    {
        CancelAfterHelper(action, delay);
        return action;
    }

    static public IAsyncOperation<T>
        CancelAfter<T>(this IAsyncOperation<T> op, TimeSpan delay)
    {
        CancelAfterHelper(op, delay);
        return op;
    }
}

var picker = new FileOpenPicker { FileTypeFilter = { ".txt" } };
StorageFile file;
try {
    file = await picker.PickSingleFileAsync().CancelAfter(TimeSpan.FromSeconds(3));
} catch (TaskCanceledException) {
    file = null;
}

4 comments

Comments are closed.

  • Avatar
    Fleet Command

    Ah, I see that you have decided to move on from “the old new thing” to “the new unpopular thing”. I’m not judging though.

    But I do have a question. Shouldn’t this material be part of the essentials taught to whoever taking a WRL+UWP course?

    • Raymond Chen
      Raymond ChenMicrosoft logo

      If you’re a C# developer, then tasks cancel in the normal way, nothing new to learn. This is under-the-hood nonsense, which may help you understand how the pieces fit together, but not necessary to know how to use it. (And I think 7 years counts as old enough.)

    • Avatar
      switchdesktopwithfade@hotmail.com

      Nobody is writing advanced Windows programming books anymore, especially Jeffrey Richter who used to nail these threading topics with amazing lucidity. Windows Internals 7 Part 2 has been sitting in limbo for years. Everyone of value was moved to “the cloud” leaving desktop to rot and die. The only way to learn Windows desktop programming is through free source code, the plain documentation which tells you none of the side effects or threading dependencies, and this miraculous blog. Extremely frustrating, but Win32 will live on.

  • Avatar
    Robin Sue

    > They all boil down to “Return from the function immediately, and resume execution when this asynchronous thing produces a value.”

    Although one should clarify that if the expression to the right of the await keyword is a method (as in most cases), the method will be called and executes synchronously until it produces the Task, that is to be awaited on, before returning immediately (ignoring the synchronously completed fast path).