Skip to content

Instantly share code, notes, and snippets.

@taylorhutchison
Created December 14, 2021 03:16
Show Gist options
  • Save taylorhutchison/4eeae0e7302cdbaa06f0b42e14cc04b8 to your computer and use it in GitHub Desktop.
Save taylorhutchison/4eeae0e7302cdbaa06f0b42e14cc04b8 to your computer and use it in GitHub Desktop.
C# example of using a lock to prevent multiple threads from starting a long running task when it only needs to be run once.
// C# top-level statements
// The goal of this code is to create a method that simulates a long-running task that
// only needs to be computed once, but that multiple threads might request before the computation is finished.
// It uses a lock object to check if the computation has been requested. If not it starts the work.
var t1 = Task.Run(async () =>
{
var result = await TaskRunner.LongRunningTask();
Console.WriteLine(result);
});
var t2 = Task.Run(async () =>
{
var result = await TaskRunner.LongRunningTask();
Console.WriteLine(result);
});
var t3 = Task.Run(async () =>
{
var result = await TaskRunner.LongRunningTask();
Console.WriteLine(result);
});
Task.WaitAll(t1, t2, t3);
public static class TaskRunner
{
private static TaskCompletionSource<int?> _tsc = new TaskCompletionSource<int?>();
private static bool _requested = false;
private static object _lockObj = new object();
public static async Task<int?> LongRunningTask()
{
lock (_lockObj)
{
if (!_requested)
{
_requested = true;
Task.Factory.StartNew(async () =>
{
await Task.Delay(10000);
var result = new Random().Next();
_tsc.SetResult(result);
});
}
}
return await _tsc.Task;
}
}
@askingalot
Copy link

It seems kinda odd to be caching a value in a Task, but I'm not sure that's wrong either.

Your solution works. I think you can do a few things to make it a little more performant.

  1. Check whether _tsc.Task is completed at the beginning of the method.
  2. Add a check of _requested above the lock
  3. Don't make LongRunningTask an async method. Your code implicitly wraps a the _tsc.Task in another Task and it doesn't need to.

There might be some clever way of using the Task.Status to avoid having the _requested flag, but I'm not sure it's worth doing that.

The real issue I see here is error handling. Can this long running task fail? If so, should that crash everything? Should you retry? I think the code you have now will cause all your threads to wait forever if there's a failure.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment