Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to create and use a custom Awaitable in c#?

I'm trying to implement a custom awaiteable to execute await Thread.SleepAsync() without creating any additional threads.

Here's what I've got:

    class AwaitableThread : INotifyCompletion
    {
        public AwaitableThread(long milliseconds)
        {
            var timer = new Timer(obj => { IsCompleted = true; }, null, milliseconds, Timeout.Infinite);
        }

        private bool isCompleted = false;

        public bool IsCompleted
        {
            get { return isCompleted; }
            set { isCompleted = value; }
        }

        public void GetResult()
        {}

        public AwaitableThread GetAwaiter() { return this; }

        public void OnCompleted(Action continuation)
        {
            if (continuation != null)
            {
                continuation();
            }
        }
    }

And here's how the sleep would work:

    static async Task Sleep(int milliseconds)
    {
        await new AwaitableThread(milliseconds);
    }

The problem is that this function returns immidiatly, even though in OnCompleted, IsCompleted is still false.

What am I doing wrong?

like image 432
Arsen Zahray Avatar asked Dec 26 '22 14:12

Arsen Zahray


1 Answers

Fully implementing the awaitable pattern for production use is a tricky business - you need to capture the execution context, amongst other things. Stephen Toub's blog post on this has a lot more detail. In many cases, it's easier to piggy-back onto Task<T> or Task, potentially using TaskCompletionSource. For example, in your case, you could write the equivalent of Task.Delay like this:

public Task MyDelay(int milliseconds)
{
    // There's only a generic TaskCompletionSource, but we don't really
    // care about the result. Just use int as a reasonably cheap version.
    var tcs = new TaskCompletionSource<int>();

    Timer timer = new Timer(_ => tcs.SetResult(0), null, milliseconds,
                            Timeout.Infinite);
    // Capture the timer variable so that the timer can't be garbage collected
    // unless the task is (in which case it doesn't matter).
    tcs.Task.ContinueWith(task => timer = null);

    return tcs.Task;
}

You can now await that task, just like you can await the result of Task.Delay.

like image 174
Jon Skeet Avatar answered Jan 12 '23 08:01

Jon Skeet