Skip to content

Instantly share code, notes, and snippets.

@eternityowo
Last active February 9, 2024 17:59
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save eternityowo/3579e84cf690085271b00af04701aefb to your computer and use it in GitHub Desktop.
Save eternityowo/3579e84cf690085271b00af04701aefb to your computer and use it in GitHub Desktop.
Deadlock and Task.WhenAll. Don't forget to use Task.Run or Task.Factory.StartNew
using System.Diagnostics;
using System.Threading.Channels;
await TestDeadlock();
return;
var tests = new (string, Func<Task>)[]
{
(nameof(TestSync), () => TestSync()),
(nameof(TestDelayAndYield), () => TestDelayAndYield()),
(nameof(TestSmallDelayAndConfigureAwaitForZero), () => TestSmallDelayAndConfigureAwaitForZero()),
(nameof(TestTaskRun), () => TestTaskRun())
};
var sw = new Stopwatch();
PrintPid(true, Scope.Main);
foreach (var (testName, test) in tests)
{
sw.Restart();
PrintPid(true, Scope.Test, testName);
await test.Invoke();
PrintPid(false, Scope.Test);
sw.Stop();
Console.WriteLine(" total sleep: {0} ms", sw.ElapsedMilliseconds / 1000M);
}
PrintPid(false, Scope.Main);
return;
async Task<int> MethodAsync(int taskId, int sleepMs, int delayMs = 0, bool safeCtx = true, bool yield = false)
{
var taskIdStr = $"tid: {taskId,2}, ";
var taskInfo = $"sleep: {sleepMs}, delay: {delayMs}, safeCtx: {safeCtx,5}, yield: {yield}";
PrintPid(true, Scope.Task, taskIdStr + taskInfo);
if (yield)
{
await Task.Yield();
}
else
{
await Task.Delay(delayMs).ConfigureAwait(safeCtx);
}
Thread.Sleep(sleepMs);
PrintPid(false, Scope.Task, taskIdStr);
return (int)Math.Sqrt(sleepMs);
}
/// <summary>
/// Sync execution of both task.
/// </summary>
async Task TestSync()
{
var task0 = MethodAsync(0, 1000);
var task1 = MethodAsync(1, 2000);
await Task.WhenAll(task0, task1);
}
/// <summary>
/// First escape by delay, second escape by yield.
/// Leave caller thread after await some task inside
/// </summary>
async Task TestDelayAndYield()
{
var task0 = MethodAsync(0, 1000, 10);
var task1 = MethodAsync(1, 2000, yield: true);
await Task.WhenAll(task0, task1);
}
/// <summary>
/// First escape by small delay, second stay on main.
/// Leave caller thread after await some task inside
/// </summary>
async Task TestSmallDelayAndConfigureAwaitForZero()
{
var task0 = MethodAsync(0, 1000, 1);
var task1 = MethodAsync(1, 2000, 0, safeCtx: false);
await Task.WhenAll(task0, task1);
}
/// <summary>
/// Use the Task.Run(), Luke!
/// Both task start in another thread from begin
/// </summary>
async Task TestTaskRun()
{
var task0 = Task.Run(() => MethodAsync(0, 1000));
var task1 = Task.Factory.StartNew(() => MethodAsync(1, 2000));
await Task.WhenAll(task0, task1);
}
/// <summary>
/// This case show how can we still get deadlock with async\await inside in method.
/// That is <see cref="TestSmallDelayAndConfigureAwaitForZero"/> and <see cref="TestDelayAndYield"/>
/// can be depends on inner behavior of awaited method. We can get the different result.
/// </summary>
async Task TestDeadlock()
{
var source = new CancellationTokenSource(TimeSpan.FromSeconds(5));
var channel = Channel.CreateBounded<int>(100);
var writeTask = new Task(async () => // Task.Run(async () =>
{
try
{
foreach (var i in Enumerable.Range(0, 10000))
{
await channel.Writer.WriteAsync(i, source.Token);
}
}
catch (Exception ex)
{
Console.WriteLine("U think u can exit by timeout? But u got lock on main thread");
}
finally
{
channel.Writer.TryComplete();
}
}, TaskCreationOptions.PreferFairness | TaskCreationOptions.LongRunning);
var readTask = new Task(async () => // Task.Run(async () =>
{
try
{
var sum = 0;
Console.Write("calc sum");
while (await channel.Reader.WaitToReadAsync(source.Token))
{
var i = await channel.Reader.ReadAsync(source.Token);
sum += i;
Console.Write(new string('.', (i % 3)+1).PadRight(3));
Console.SetCursorPosition(Console.CursorLeft - 3, Console.CursorTop);
}
Console.WriteLine();
Console.WriteLine($"sum: {sum}");
}
catch (Exception ex)
{
Console.WriteLine("U think u can exit by timeout? But u got lock on main thread");
}
}, TaskCreationOptions.PreferFairness | TaskCreationOptions.LongRunning);
await Task.WhenAll(writeTask, readTask);
}
void PrintPid(bool @in, Scope scope, string taskInfo = "")
{
var pad = new string(' ', (int)scope);
var way = @in ? "in" : "out";
var pid = Thread.GetCurrentProcessorId();
Console.WriteLine($"{pad}{way,-4}({scope}) pid: {pid,2}. {taskInfo}");
}
enum Scope
{
Main = 0,
Test = 4,
Task = 8
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment