I'm pulling my hair out on this, and it's probably just because I haven't had that Ah-Ha moment yet. Help my Ah-Ha moment? I've distilled the example down into a small WebAPI demo project. It's .NET7 C# - nothing unusual about it. If you're following along, all the important bits could be generated in a console app just as well.
I'm using swagger to call a controller called GenerateDemoDataAsync
, the controller calls off to a service instance, the service does some simulated heavy lifting, prints a whole bunch of debugging type console writes to and returns.
builder.Services.AddTransient<DemoDataService>();
[HttpPost("api/generatedemodataasync")]
public async Task<int> PostAsync()
{
await demoData.GenerateDemoDataAsync(InstanceId);
return 0;
}
// string instance - a temporary unique identifier so when
// multiple are running in the console, I can tell which "sets"
// below together
public async Task GenerateDemoDataAsync(string instance)
{
Stopwatch sw = new Stopwatch();
sw.Start();
await Task.Run(() => // Is this even needed?
{
for(int i = 0; i <= 99; i++)
{
Console.WriteLine($"GenerateDemoDataAsync_InLoop:: {i}");
await InsertDataAsync(instance, i.ToString());
// Thread.Sleep(10000);
await Task.Delay(10000); // In some scenarios this seems to be completely
// ignored and I must use the Thread.Sleep
});
});
sw.Stop();
Console.WriteLine($"GenerateDemoDataAsync_End :: Time Elapsed {sw.Elapsed}");
}
private async Task InsertDataAsync(string instance, string message)
{
await Task.Run(() => // Internal Operation is not async, Task.Run
// should this be passed off as a Task, an async Task, not at all?
{
var item = new DataQueueItem() { Instance = instance, Message = message };
Console.WriteLine($"InsertDataAsync_InRun generated item:: {instance}:{message}");
});
}
I've configured about 100 different combinations of async Task Function and callers with async await, or Task.Run(() =>)
. In the current implementation of the code, I would expect it to do this:
POST => (calling async operation)
GENERATE_DEMO_DATA_ASYNC => drops into a background task, allowing control to be passed back to the UI while the for loop finishes. => PASS CONTROL BACK TO POST METHOD while async operation continues.
INSERT_DATA_ASYNC => Called by above, it may or may not have any bearing on this. It doesn't seem to make a difference. If I comment out the following line, the behavior seems the same.
What happens is, the whole thing ZIPS through like lightning, the Thread.Sleep
or Task.Delay
seem completely ignored....or if I move things around some and decorate some async stuff into some of the callers like the anonymous function in the Task.Run()
, then Thread.Sleep
or Task.Delay
is honored, but it will block the POST from returning until the entire operation is returned.
await InsertDataAsync(instance, i.ToString());
As documented in the code, there are several places where I don't know if I'm helping myself or shooting myself in the foot with Task.Run(() =>
operations. I'm probably thinking of it all wrong, and I come from a long C# and C++ background (30+ years), so it's probably my own background causing assumptions that are incorrect. I know there's some crazy smart people about. I know I could personally even much around with the combinations to get exactly what I wanted, but I'd LOVE it if someone would explain and deep dive into it and help me understand it so I can actually architect async / await pattern flow, rather than just mucking with it till it works.
Thank you everyone!