Дано: сервер, который может делать запросы на обслуживание.Сервис может выполнить только один запрос за раз.Я хочу кэшировать запущенную задачу для обслуживания, например, если сервер выполняет запрос для первого клиента и получает запрос от второго клиента, сервер вернет уже созданную задачу второму клиенту вместо ожидания завершения первой задачи и запуска второго запроса.
Первая версия была с замками:
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;
}
}
Но она не выглядела хорошо для меня.Поэтому я переписал его с помощью Interlocked:
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, ig повторно использовать его и не писать для каждого использования (потому что оно подвержено ошибкам)?
- может быть, лучшерешение?
Использование:
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);
}