Blazor에서 발생할 수 있는 async void 메서드의 부작용
지난 글을 통해 ASP.NET Core Web 환경에서 async void 메서드를 사용한 것에 대한 부작용을 설명했는데요,
ASP.NET Core Web API / Razor 페이지에서 발생할 수 있는 async void 메서드의 부작용
; https://www.sysnet.pe.kr/2/0/13885
이와 유사한 문제를 Blazor에서도 겪게 됩니다. 예를 들어 Blazor 프로젝트 생성 시 기본 작성되는 Counter.razor 파일은,
@page "/counter"
@rendermode InteractiveServer
<PageTitle>Counter</PageTitle>
<h1>Counter</h1>
<p role="status">Current count: @currentCount</p>
<button class="btn btn-primary" @onclick="IncrementCount">Click me</button>
@code {
private int currentCount = 0;
private void IncrementCount()
{
currentCount++;
}
}
기본적으로 동기 방식의 메서드로 정의돼 있는데요, 만약 해당 메서드 내부의 코드에서 await을 사용하고 싶어 무심코 async void로 정의한다면,
@code {
private int currentCount = 0;
private async void IncrementCount()
{
await Task.Delay(1000);
currentCount++;
}
}
Blazor 내부 프레임워크에서는 저 메서드에 대한 호출을 InvokeAsync로 경유합니다.
// src/Components/Components/src/EventCallbackWorkItem.cs
// https://github.com/dotnet/aspnetcore/blob/main/src/Components/Components/src/EventCallbackWorkItem.cs
internal static Task InvokeAsync<T>(MulticastDelegate? @delegate, T arg)
{
switch (@delegate)
{
case null:
return Task.CompletedTask;
case Action action:
action.Invoke(); // async void로 정의한 메서드 호출
return Task.CompletedTask;
case Action<T> actionEventArgs:
actionEventArgs.Invoke(arg);
return Task.CompletedTask;
case Func<Task> func: // async Task로 정의한 메서드 호출
return func.Invoke();
case Func<T, Task> funcEventArgs:
return funcEventArgs.Invoke(arg);
default:
{
try
{
return @delegate.DynamicInvoke(arg) as Task ?? Task.CompletedTask;
}
catch (TargetInvocationException e)
{
// Since we fell into the DynamicInvoke case, any exception will be wrapped
// in a TIE. We can expect this to be thrown synchronously, so it's low overhead
// to unwrap it.
return Task.FromException(e.InnerException!);
}
}
}
}
지난 글을 이해했다면, IncrementCount가 어떻게 동작하게 될지 예상할 수 있을 텐데요, 즉, "Click me" 버튼을 누르면 첫 번째 클릭에는 화면에 아무런 업데이트가 되지 않습니다. 그리고 두 번째로 누르면 이전 첫 번째 버튼 클릭으로 증가했던 "1"이 화면에 보이지만 실제 currentCount의 실제 값은 2가 된 상태로 서버에 저장돼 있는 것입니다.
참고로, Blazor에서 저 부작용이 더 위험한 이유가 바로 상태 저장이 된다는 점 때문입니다. 그래서 자칫 개발자가 테스트를 하면서 첫 번째 클릭에 대한 반응이 없는 것을 뭔가 웹 브라우저의 클릭이 단순히 무시된 것으로 착각할 수 있기 때문에 별다른 자각 없이 저 문제를 안고 릴리스하는 사태가 발생할 수 있습니다.
[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]