HiveBrain v1.2.0
Get Started
← Back to all entries
patterncsharpMinor

Implementing JQuery style 'deferred' and 'promise' in C#

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
deferredpromisestylejqueryandimplementing

Problem

I like the pattern of the jQuery Deferred object.

I like how you can call Resolve any number of times, but the listening objects will only be notified once. I also like how you can attach a listening object after Resolve has already been called, and the listening object will still get the notification. If you're programming in an uncertain multithreaded environment, it greatly reduces the amount of thinking you have to do.

I couldn't find an equivalent in the C# library, so here is my implementation (a basic subset so far):

using System;
using System.Collections.Concurrent;
using System.Threading;

interface IPromise
{
    void Done(Action action);
}

sealed class Deferred : IPromise
{
    readonly ConcurrentBag _actions = new ConcurrentBag();
    // using an int instead of a bool
    // so we can use Interlocked
    private int _isResolved = 0;

    internal void Resolve()
    {
        Interlocked.Exchange(ref _isResolved, 1);
        InvokeActions();
    }

    void IPromise.Done(Action action)
    {
        _actions.Add(action);
        if (_isResolved == 1)
        {
            InvokeActions();
        }
    }

    void InvokeActions()
    {
        Action action = null;
        while (_actions.TryTake(out action))
        {
            action();
        }
    }
}


For this code to be usable, it has to be completely 100% thread safe. Can anyone see a hole in it?

Also, am I just re-implementing something that already exists in some corner of the .NET base class library?

For the sake of context I'll provide a practical use: there's a long running application, and some code that must execute if a file exists. The file might exist when the code first runs, or might come to exist later, or might never exist at all.
I invoke it like this:

```
string fullPath = GenerateWatchedFilePath();
FileSystemWatcherUtils
.WhenFileAppears(fullPath)
.Done(() =>
{
using (var stream = File.OpenRead(fullPath))

Solution

I like how you can call Resolve any number of times, but the listening objects will only be notified once. I also like how you can attach a listening object after Resolve has already been called, and the listening object will still get the notification. If you're programming in an uncertain multithreaded environment, it greatly reduces the amount of thinking you have to do.

This sounds a lot like TaskCompletionSource along with the Task it controls.

Assuming you have a TaskCompletionSource tcs:

  • To attach a listener, use tcs.Task.ContinueWith(_ => / listener code here /). You can do this before or after the Task has been completed and the listener code will be executed only once after the Task has been completed.



  • To complete the Task (the equivalent of resolving), use tcs.TrySetResult(someValue). If you'll use the TrySetResult() variant instead of SetResult(), the second and following attempt at completing will be ignored (SetResult() would throw an exception).



The difference with your code is that TaskCompletionSource always has some result type. You can either just ignore that (using e.g. TaskCompletionSource and SetResult(null)), or use the non-generic TaskCompletionSource from AsyncEx.

Context

StackExchange Code Review Q#70965, answer score: 4

Revisions (0)

No revisions yet.