缓存运行任务

时间:2019-09-22 16:35:04

标签: c# task-parallel-library multitasking

给出:服务器,可以发出服务请求。服务一次只能执行一个请求。 我想将正在运行的任务缓存到服务中,例如如果服务器执行对第一客户的请求并接收到第二客户的请求,则服务器将已创建的任务返回给第二客户,而不是等待第一任务完成并启动第二个请求。

第一个版本带有锁:

public class Lock
{
    private readonly object _lock = new object();
    private Task<long> _task;

    public Task<long> GetServiceResultAsync()
    {
        async Task<long> GetTaskAsync()
        {
            // to execute lock scope very fast
            await Task.Yield();

            return await GetServiceResultInternalAsync();
        }

        lock (_lock)
        {
            if (_task == null ||
                _task.IsCompleted)
            {
                _task = GetTaskAsync();
                _task.ContinueWith(task =>
                {
                    lock (_lock)
                    {
                        _task = null;
                    }
                });
            }

            return _task;
        }
    }

    private async Task<long> GetServiceResultInternalAsync()
    {
        await Task.Delay(1000);
        return DateTime.Now.Ticks;
    }
}

但是对我来说看起来并不好。所以我用互锁将其重写:

public class Interlock
{
    private Lazy<Task<long>> _task;

    public Task<long> GetServiceResultAsync()
    {
        return TaskCacheHelper.GetOrCreateTask(ref _task, GetServiceResultInternalAsync, NullifyStartUpgradeTask);
    }

    private void NullifyStartUpgradeTask(Lazy<Task<long>> lazy)
    {
        Interlocked.CompareExchange(ref _task, null, lazy);
    }

    private async Task<long> GetServiceResultInternalAsync()
    {
        await Task.Delay(1000);
        return DateTime.Now.Ticks;
    }
}

public static class TaskCacheHelper
{
    public static Task<T> GetOrCreateTask<T>(ref Lazy<Task<T>> field, Func<Task<T>> getTask, Action<Lazy<Task<T>>> nullifyField)
    {
        var oldField = field;
        if (oldField != null && !oldField.Value.IsCompleted)
        {
            return oldField.Value;
        }

        // use Lazy to prevent task from executing 2 times 
        var newValue = new Lazy<Task<T>>(getTask);
        var originalFieldValue = Interlocked.CompareExchange(ref field, newValue, oldField);

        if (originalFieldValue == oldField)
        {
            // external delegate used here because ref parameter can't be used inside lambda
            newValue.Value.ContinueWith(task => nullifyField(newValue));

            return newValue.Value;
        }

        return originalFieldValue.Value;
    }
}

第二个版本看起来更好,但是我对Nullify方法感到困惑:这是常见的逻辑,但每次都应编写。

所以,问题是:

  • 是否存在将NullifyStartUpgradeTask放入TaskCacheHelper中的方法,例如重复使用它,而不是为每种用法而写(因为它容易出错)?
  • 也许有更好的解决方案?

用法:

static async Task Main(string[] args)
{
    var pr = new Interlock();
    var t1 = pr.GetServiceResultAsync();
    var t2 = pr.GetServiceResultAsync();

    await Task.WhenAll(t1, t2);
    Debug.Assert(t1.Result == t2.Result);

    var t3 = await pr.GetServiceResultAsync();
    var t4 = await pr.GetServiceResultAsync();
    Debug.Assert(t3 != t4);
}

0 个答案:

没有答案