Как я могу заставить задачи ждать того же результата? - PullRequest
1 голос
/ 04 апреля 2019

У меня есть простой синглтон-класс,

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
        */
    }
}

Поскольку это синглтон, GetRefreshedValue будет вызываться одновременно. Я хочу, чтобы одновременно выполнялось ровно одна или ноль задач GetRefreshedValueImplementation.

Само по себе это было бы просто, я мог бы использовать SemaphoreSlim.

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

Однако я хочу, чтобы каждая задача, ожидающая у ворот, получила недавно вычисленное возвращаемое значение. Я не хочу, чтобы они стояли в очереди, чтобы сделать звонок.

Как лучше написать этот код?

Ответы [ 4 ]

2 голосов
/ 04 апреля 2019
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
        */
    }
}
2 голосов
/ 04 апреля 2019

Так что сама операция достаточно проста.Вам просто нужно сохранить Task для операции при ее запуске и очистить ее после ее завершения, чтобы вы могли повторно использовать задачу во время ее выполнения.Оттуда он просто добавляет правильную синхронизацию, чтобы ее можно было безопасно использовать из нескольких потоков (я полагаю, что это необходимо, и что не все проходит через один контекст синхронизации, если это так, вы можете удалить код блокировки.)

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;
            }
        }
    }
}
0 голосов
/ 04 апреля 2019

Что-то вроде:

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
        */
    }
}
0 голосов
/ 04 апреля 2019

Насколько я понимаю, в вашем случае вы должны позволить вызовам получить результат одной и той же текущей задачи, и, если ее нет, следует создать новую. Если это так, то это послужит вашей цели:

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;
    }
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...