-
Notifications
You must be signed in to change notification settings - Fork 4.7k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Validate StartAsync() exception semantics for Starting, Start, Started #88603
Comments
Tagging subscribers to this area: @dotnet/area-extensions-hosting Issue DetailsAs part of #87335 and #88546 we should evaluate the current exception handling abort semantics between the stages which is currently:
For the current implementation, see Proposal:
Note that for Stopping\Stop\Stopped, it makes sense to continue to run each phase so that the proper clean-up work is handled, so this does not have the same semantics as Starting\Start\Starting. As a side note, we should verify when ASP.NET calls
|
@steveharter Apologies, as this is not directly related to the issue above, but I didn't feel it warranted a new issue, and the original PR is locked. I'm writing a blog post on the new lifecycle phases added with |
For the case when |
Thanks for responding with this answer. I'm not sure I fully follow though and perhaps there's nuance I'm not understanding. As the host is used in console apps, there is no specific synchronization context, so everything ends up scheduled for the ThreadPool. While the methods run synchronously until the first await, the work is then (likely immediately) scheduled onto a thread pool thread and running concurrently, even before the Console.WriteLine($"Started app on thread {Environment.CurrentManagedThreadId}");
List<Task> tasks = new();
var ops = new Func<Task>[]
{
Test.TaskOne,
Test.TaskTwo,
Test.TaskThree,
Test.TaskFour,
Test.TaskFive
};
for (var i = 0; i < ops.Length; i++)
{
Console.WriteLine($"Invoking Task {i + 1} on thread {Environment.CurrentManagedThreadId}.");
var task = ops[i]();
if (task.IsCompleted)
{
Console.WriteLine($"Task {i + 1} on thread {Environment.CurrentManagedThreadId} is completed synchronously.");
}
else
{
Console.WriteLine($"Task {i + 1} running asynchronously.");
tasks.Add(task);
}
}
Console.WriteLine($"Waiting for keypress.");
Console.ReadKey();
Console.WriteLine($"Calling WhenAll.");
await Task.WhenAll(tasks).ConfigureAwait(false);
Console.WriteLine($"Finished app on thread {Environment.CurrentManagedThreadId}.");
public static class Test
{
public static Task TaskOne()
{
Console.WriteLine($"TASK ONE: Starting on thread {Environment.CurrentManagedThreadId}.");
return Task.CompletedTask;
}
public static async Task TaskTwo()
{
Console.WriteLine($"TASK TWO: Starting on thread {Environment.CurrentManagedThreadId}.");
await Task.Yield();
Console.WriteLine($"TASK TWO: After Yield on thread {Environment.CurrentManagedThreadId}.");
await Task.Delay(2000);
Console.WriteLine($"TASK TWO: Completed after 2s delay on thread {Environment.CurrentManagedThreadId}.");
}
public static async Task TaskThree()
{
Console.WriteLine($"TASK THREE: Starting on thread {Environment.CurrentManagedThreadId}.");
await Task.Yield();
Console.WriteLine($"TASK THREE: After Yield on thread {Environment.CurrentManagedThreadId}.");
await Task.Delay(1000);
Console.WriteLine($"TASK THREE: Completed after 1s delay on thread {Environment.CurrentManagedThreadId}.");
}
public static Task TaskFour()
{
Console.WriteLine($"TASK FOUR: Starting on thread {Environment.CurrentManagedThreadId}.");
return Task.CompletedTask;
}
public static async Task TaskFive()
{
Console.WriteLine($"TASK FIVE: Starting on thread {Environment.CurrentManagedThreadId}.");
await Task.Yield();
Console.WriteLine($"TASK FIVE: After Yield on thread {Environment.CurrentManagedThreadId}.");
await Task.Delay(5000);
Console.WriteLine($"TASK FIVE: Completed after 5s delay on thread {Environment.CurrentManagedThreadId}.");
}
} runs generally as follows:
|
The host is generic - can be hosted by console app, ASP.NET, service, etc where they can have a custom task scheduler. However perhaps the use of |
As part of #87335 and #88546 we should evaluate the current exception handling abort semantics between the stages which is currently:
IHostApplicationLifetime.ApplicationStarted
is not called.For the current implementation, see
StartAsync()
here.Proposal: If there is an exception in Starting, Start or Started, processing stops and the next stage is not run. The exception is logged and re-thrown. For Started, if there is an exception then
IHostApplicationLifetime.ApplicationStarted
is not run which is consistent with the prior behavior (before the new stages were added in #87335) if there was an exception in Start.Note that for Stopping\Stop\Stopped, it makes sense to continue to run each phase so that the proper clean-up work is handled, so this does not have the same semantics as Starting\Start\Starting. As a side note, we should verify when ASP.NET calls
StopAsync()
if there is an exception inStartAsync()
.The text was updated successfully, but these errors were encountered: