简体   繁体   中英

How can I make tasks wait for the same result?

I have a simple singleton class,

public class SimpleSingleton
{
    public async Task<int> GetRefreshedValue()
    {
        /*
            What goes here?
        */
        return await GetRefreshedValueImplementation();
        /*
            What goes here?
        */
    }

    private async Task<int> GetRefreshedValueImplementation()
    {
        /*
           Resource intensive and not thread safe
        */
    }
}

Because this is a singleton, GetRefreshedValue will be called concurrently. I want exactly one or zero tasks to be performing GetRefreshedValueImplementation at a time.

In itself that would be simple, I could use a SemaphoreSlim .

private static SemaphoreSlim gate = new SemaphoreSlim(1);
...
await gate.WaitAsync();
try
{
    return await GetRefreshedValueImplementation();
}
finally
{
    gate.Release();
}

However, I want every task that is waiting at the gate to get the recently calculated return value. I don't want them to queue up to make the call.

What is the best way of writing that code?

So the operation itself is simple enough. You just need to store the Task for the operation when you start it, and clear it when it finishes, so that you can re-use the task while it's running. From there it's just adding the proper synchroniation so that it's safe to use from multiple threads (I assume that's needed, and that this isn't all going through a single synchronization context, if it is, you can strip out the locking code.)

public class Foo<T> //TODO come up with good name
{
    private Func<Task<T>> factory;
    private Task<T> currentInvocation;
    private object key = new object();
    public Foo(Func<Task<T>> factory)
    {
        this.factory = factory;
    }
    public Task<T> Value
    {
        get
        {
            lock (key)
            {
                if (currentInvocation == null)
                {
                    currentInvocation = factory();
                    currentInvocation?.ContinueWith(_ =>
                    {
                        lock (key) { currentInvocation = null; }
                    });
                }
                return currentInvocation;
            }
        }
    }
}
public class SimpleSingleton
{
    private static Task<int> executingTask;
    private static object lockObject = new object();

    public async Task<int> GetRefreshedValue()
    {
        lock (lockObject)
            {
                if (executingTask == null || executingTask.IsCompleted)
                {
                    executingTask = GetRefreshedValueImplementation();
                }
            }
        return await executingTask;
    }

    private async Task<int> GetRefreshedValueImplementation()
    {
        /*
           Resource intensive and not thread safe
        */
    }
}

As of my understanding to your case, you need to let the calls get the result of the same on-going task, and if none, a new one should be created. If that's the case then, this would serve your purpose:

public class SimpleSingleton
{
    private SimpleSingleton() { }
    private static SimpleSingleton _instance;
    public static SimpleSingleton Instance => _instance ?? (_instance = new SimpleSingleton());
    public async Task<int> GetRefreshedValue()
    {
        return await GetRefreshedValueImplementation();
    }
    private volatile Task<int> _getRefreshedValueImplementationTask;
    private Task<int> GetRefreshedValueImplementation()
    {
        if (_getRefreshedValueImplementationTask is null || _getRefreshedValueImplementationTask.IsCompleted)
        {
            return _getRefreshedValueImplementationTask = Task.Run(async () =>
            {
                /*
                   Resource intensive and not thread safe
                */
                int r = new Random().Next(1000, 2001);
                await Task.Delay(r);
                return r;
            });
        }
        return _getRefreshedValueImplementationTask;
    }
}

Something like:

public class SimpleSingleton
{
    private int _sequenceNo;
    private int _lastvalue;
    private object _lock = new object;

    public async Task<int> GetRefreshedValue()
    {
        var currentSeq = _sequenceNo;
        lock(_lock)
        {
           if (_sequenceNo == currentSeq)
           {
              _lastValue = await GetRefreshedValueImplementation();
              _sequenceNo++;
           }
        }
        return _lastValue;
    }

    private async Task<int> GetRefreshedValueImplementation()
    {
        /*
           Resource intensive and not thread safe
        */
    }
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM