GitHub Sample
(Sample은 임의로 구현했습니다. => await Task.Run(ScanAndGetResult)
Page의 Contructor에서 Item을 추가합니다.
Item 추가하는 시간이 길어서 1번 페이지에서 멈췄다가 Page가 열립니다.
Page가 열리고 Item이 추가되면 좋을 것 같습니다.
Wifi를 Scan한 값을 받아서 item을 추가하는 매소드가 있습니다. (다른 기기의 Wifi 검색 값입니다.)
문제는 이 매소드가 ViewModel - Constructor
에 있고
(Thread에 문제가 있어서) MainThread로 돌리고 있습니다.
그래서 UI가 멈추고, Scan과 item이 완료되면 페이지가 열립니다.
public Constructor()
{
MainThread.InvokeOnMainThreadAsync(AddItem);
}
AddItem가 끝나야 Page가 열립니다.
[Xaml]
<Toolkit:EventToCommandBehavior
EventName="Appearing"
Command="{Binding AddWifiResultCommand}" />
[RelayCommand]
async Task AddItem()
{
await WifiScan(() => {item.add(WifiResult}).ConfigureAwait(false);
}
Appearing event
를 사용하면 앱이 죽습 니다.
=> Error : MonoDroid android.runtime.JavaProxyThrowable: System.TypeLoadException: Could not resolve type with token 01000164 from typeref (expected class 'System.Net.NetworkInformation.PhysicalAddress' in assembly 'System.Net.NetworkInformation, Version=6.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a') at Microsoft.Maui.ApplicationModel.MainThread.BeginInvokeOnMainThread(Action ) at CommunityToolkit.Mvvm.Input.RelayCommand.Execute(Object ) at CommunityToolkit.Maui.Behaviors.EventToCommandBehavior.OnTriggerHandled(Object sender, Object eventArgs) at Microsoft.Maui.Controls.Page.SendAppearing() at CommunityToolkit.Maui.Behaviors.EventToCommandBehavior.OnTriggerHandled(Object sender, Object eventArgs) at Microsoft.Maui.Controls.Page.SendAppearing() at Microsoft.Maui.Controls.ShellSection.PresentedPageAppearing() at Microsoft.Maui.Controls.ShellSection.<PresentedPageAppearing>g__OnPresentedPageParentSet|100_0(Object sender, EventArgs e) at Microsoft.Maui.Controls.Element.OnParentSet() at Microsoft.Maui.Controls.NavigableElement.OnParentSet() ...
1개의 좋아요
해결과는 별개로!
@Stupid 샘플로 간단하게 간소화해서 (GitHub) 공유해주시면 구조적으로 좋은 답들을 많이 찾으실 수 있을 것 같습니다!!
1개의 좋아요
dimohy
11월 14, 2022, 10:08오전
3
제가 MAUI로 개발하고 있지 않기도 하고 정확한 상황이 파악 되지 않아 일반적인 접근으로 말씀드리면,
오래 걸리는 작업의 처리는 UI 스레드가 아닌 다른 스레드에서 처리하도록 하고
그 결과만 UI 스레드에 반영하도록 하면 봉착하고 있는 문제가 해결될 것으로 생각합니다.
이 원칙으로 위의 코드를 보았을 때 다음과 같이 수정하면 되지 않을까요?
public Constructor()
{
_ = AddItem();
}
[RelayCommand]
async Task AddItem()
{
await WifiScan(() => MainThread.BeginInvokeOnMainThread(() => item.add(WifiResult)).ConfigureAwait(false);
}
※ 테스트하지 않은 코드이므로… 참고만 해주세요. 곧 간단한 샘플을 만들어서 좀 더 정확한 답을 할 수 있도록 하겠습니다. 예를 들어 MAUI에서도 SynchronizationContext로 인해 UI 스레드에서 await이후 UI 스레드로 복귀되는지 등은 확인이 필요할 것 같습니다.
4개의 좋아요
dimohy
11월 14, 2022, 11:01오전
4
간단하게 코드를 구현해서 동작성을 확인하였습니다.
제 생각에 오래 걸리는 작업의 시작 지점은 Page의 Loaded
지점이 좋아 보입니다.
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage
x:Class="MauiApp31.MainPage"
xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
Loaded="ContentPage_Loaded">
...
private void ContentPage_Loaded(object sender, EventArgs e)
{
_ = ViewModel.LoadAsync();
}
ViewModel의 LoadAsync()는 다음처럼 구현하였습니다. 오래 걸리는 동작을 Thread.Sleep()
으로 시뮬레이션 하였습니다.
public async Task LoadAsync()
{
ResultText = $"Processing... ({Environment.CurrentManagedThreadId})";
IsBusy = true;
//await Task.Delay(3000);
//await Task.Run(() => Thread.Sleep(3000)).ConfigureAwait(false);
await Task.Run(() => Thread.Sleep(3000));
IsBusy = false;
ResultText = $"Complete! ({Environment.CurrentManagedThreadId})";
}
확인한 경과 당연? 하게도 MAUI에서도 SynchronizationContext에 의해서 await 이후 UI 스레드로 복귀합니다.
| 동작화면
안드로이드에서도 잘 동작합니다.
| MainPage.xaml
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage
x:Class="MauiApp31.MainPage"
xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
Loaded="ContentPage_Loaded">
<ScrollView>
<Grid>
<VerticalStackLayout
Padding="30,0"
Spacing="25"
VerticalOptions="Center">
<Image
HeightRequest="200"
HorizontalOptions="Center"
SemanticProperties.Description="Cute dot net bot waving hi to you!"
Source="dotnet_bot.png" />
<Label
FontSize="32"
HorizontalOptions="Center"
SemanticProperties.HeadingLevel="Level1"
Text="Hello, World!" />
<Label
FontSize="18"
HorizontalOptions="Center"
SemanticProperties.Description="Welcome to dot net Multi platform App U I"
SemanticProperties.HeadingLevel="Level2"
Text="Welcome to .NET Multi-platform App UI" />
<Button
x:Name="CounterBtn"
Clicked="OnCounterClicked"
HorizontalOptions="Center"
SemanticProperties.Hint="Counts the number of times you click"
Text="Click me" />
<Label
FontSize="64"
HorizontalTextAlignment="Center"
Text="{x:Binding ResultText,
Mode=OneWay}" />
</VerticalStackLayout>
<ActivityIndicator
Background="Black"
IsRunning="True"
IsVisible="{x:Binding IsBusy,
Mode=OneWay}"
Opacity="0.8" />
</Grid>
</ScrollView>
</ContentPage>
| MainPageXaml.cs
namespace MauiApp31;
public partial class MainPage : ContentPage
{
private MainViewModel ViewModel { get; }
int count = 0;
public MainPage()
{
InitializeComponent();
ViewModel = new MainViewModel();
BindingContext = ViewModel;
}
private void OnCounterClicked(object sender, EventArgs e)
{
count++;
if (count == 1)
CounterBtn.Text = $"Clicked {count} time";
else
CounterBtn.Text = $"Clicked {count} times";
SemanticScreenReader.Announce(CounterBtn.Text);
}
private void ContentPage_Loaded(object sender, EventArgs e)
{
_ = ViewModel.LoadAsync();
}
}
| MainViewModel.cs
namespace MauiApp31
{
[INotifyPropertyChanged]
public partial class MainViewModel
{
[ObservableProperty]
private bool _isBusy = false;
[ObservableProperty]
private string _resultText;
public async Task LoadAsync()
{
ResultText = $"Processing... ({Environment.CurrentManagedThreadId})";
IsBusy = true;
//await Task.Delay(3000);
//await Task.Run(() => Thread.Sleep(3000)).ConfigureAwait(false);
await Task.Run(() => Thread.Sleep(3000));
IsBusy = false;
ResultText = $"Complete! ({Environment.CurrentManagedThreadId})";
}
}
}
3개의 좋아요
Stupid
11월 14, 2022, 11:20오전
5
샘플 감사합니다.
Loaded
에서 앱이 죽는지 확인 해봐야할 것 같네요.
OnAppearing
event에서 ScanAndGetReuslt
를하면 앱이 죽었거든요…
2개의 좋아요
dimohy
11월 14, 2022, 11:23오전
6
관련해서 좀 더 부연 설명 하자면,
_ = AsyncTaskMethod();
의 스레드 동작과
Task.Run(() => …);
의 스레드 동작이 다릅니다.
첫번째는 AsyncTaskMethod()에서 최초 await를 만날 때까지 메소드 진입 스레드에서 코드를 실행을 하고,
두번째는 스레드 풀의 다른 스레드에서 시작합니다.
async/await를 사용한 코드가 어떻게 변환되는지를 알면 혼돈하지 않는 명료한 부분이지만 저도 처음 async/await를 사용하기 시작했을 때 꽤 혼란스러웠습니다.
이부분은 정성태님의 아래 글을 참고하면 좋습니다.
.NET Framework: 716. async 메서드의 void 반환 타입 사용에 대하여 (sysnet.pe.kr)
.NET Framework: 698. C# 컴파일러 대신 직접 구현하는 비동기(async/await) 코드 (sysnet.pe.kr)
그리고 또 SynchronizationContext 때문에 자칫 의도치 않는 동작성을 보일 수 있습니다. 의도치않게 await이후 UI 스레드에서 코드가 동작할 것이기 때문입니다. 관련해서 아래의 글을 참고하면 좋습니다.
.NET Framework: 720. 비동기 메서드 내에서 await 시 ConfigureAwait 호출 의미 (sysnet.pe.kr)
3개의 좋아요
위에 훌륭한 내용으로 해결이 된듯 한데
위 질문의 답변과는 별개로 위 인용 글과 관련해서 WPF 환경에서
새 윈도우가 Open될때 데이터를 비동기적으로 처리하는 방법에 대해 정리했던 글을 공유해 봅니다.
5개의 좋아요
근본적으로 constructor에서 해당 잡을 실행하지 않으면 됩니다.
ViewModel에 Scan을 시작하는 비동기 메서드를 만들어두고 OnAppering 이벤트 받을 때 Scan하는 것도 방법이에요.
왜냐하면 Constructor는 객체가 살아있는 동안 한 번만 호출할텐데 WiFi Scan의 경우 한 번만 호출하지는 않을 것 같거든요.
2개의 좋아요
덧붙여서 WiFi 객체를 ViewModel에서 관리하는 것은 비용이 많이 들기 때문에 SSID, MAC ADDR과 같은 필요한 정보만 ViewModel에서 가지고 있는 것이 좋을 것 같아요.
2개의 좋아요