Created
December 14, 2021 03:16
-
-
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.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
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.
_tsc.Task
is completed at the beginning of the method._requested
above thelock
LongRunningTask
anasync
method. Your code implicitly wraps a the_tsc.Task
in anotherTask
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.