I've been doing some testing with the TPL in C# to better understand how threads are created and when exactly new threads are kicked off.
With the below tests, The first one, using no async/await
behaves as expected and always returns the values I would expect. However, the final 2 tests using async/await
are very inconsistent.
In my tests, I made the following assumptions:
GetThreadIdInstant
would return on the same thread due to the task being completetd.GetThreadIdDelayed
would return on a different thread due to the delay not returning instantly.GetThreadIdForcedNew
would return on a different thread due to the use ofTask.Run()
.
Is there an explanation why the above assumptions are true when using .Result
on a task, but not consistently true when using async/await
?
Edit: Clarification on "inconsistent" tests: So the last 2 tests using async/await
, I still expected them to give the same results as the first test using .Result
, which is not true. However, the reason I have the code inside of a for
loop is that some iterations work, and then later on my Assert
statement will fail. The reason I used the word "inconsistent" is b/c continuously running the tests, and alternating between just running them versus debugging them causes them to sometimes pass and sometimes fail.
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System;
using System.Threading;
using System.Threading.Tasks;
namespace Parallels.Tests
{
[TestClass]
public class GetterTests
{
//this test always succeeds
[TestMethod]
public void ResultTest()
{
for (var i = 0; i < 500; i++)
{
var currentThreadId = Thread.CurrentThread.ManagedThreadId;
var instantThreadId = ThreadGetter.GetThreadIdInstant().Result;
var delayedThreadId = ThreadGetter.GetThreadIdDelayed().Result;
var forcedNewThreadId = ThreadGetter.GetThreadIdForcedNew().Result;
Assert.AreEqual(currentThreadId, instantThreadId);
Assert.AreNotEqual(currentThreadId, delayedThreadId);
Assert.AreNotEqual(currentThreadId, forcedNewThreadId);
}
}
//mixed results
[TestMethod]
public async Task AwaitDelayedTest()
{
for (var i = 0; i < 500; i++)
{
try
{
var currentThreadId = Thread.CurrentThread.ManagedThreadId;
var delayedThreadId = await ThreadGetter.GetThreadIdDelayed();
Assert.AreNotEqual(currentThreadId, delayedThreadId);
}
catch (Exception ex)
{
throw new Exception($"failed at iteration: {i}", ex);
}
}
}
//mixed results
[TestMethod]
public async Task AwaitForcedNewTest()
{
for (var i = 0; i < 500; i++)
{
try
{
var currentThreadId = Thread.CurrentThread.ManagedThreadId;
var forcedNewThreadId = await ThreadGetter.GetThreadIdForcedNew();
Assert.AreNotEqual(currentThreadId, forcedNewThreadId);
}
catch (Exception ex)
{
throw new Exception($"failed at iteration: {i}", ex);
}
}
}
}
public static class ThreadGetter
{
public static async Task<int> GetThreadIdInstant() => Thread.CurrentThread.ManagedThreadId;
public static async Task<int> GetThreadIdDelayed()
{
await Task.Delay(1);
return Thread.CurrentThread.ManagedThreadId;
}
public static async Task<int> GetThreadIdForcedNew() => await Task.Run(() => Thread.CurrentThread.ManagedThreadId);
}
}