Page가 열리고 난 뒤에 Item을 추가하고 싶습니다.

SampleTest

GitHub Sample
(Sample은 임의로 구현했습니다. => await Task.Run(ScanAndGetResult)

  1. Page의 Contructor에서 Item을 추가합니다.
  2. Item 추가하는 시간이 길어서 1번 페이지에서 멈췄다가 Page가 열립니다.
  3. 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) 공유해주시면 구조적으로 좋은 답들을 많이 찾으실 수 있을 것 같습니다!! :smile:

1개의 좋아요

제가 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개의 좋아요

간단하게 코드를 구현해서 동작성을 확인하였습니다.

제 생각에 오래 걸리는 작업의 시작 지점은 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 스레드로 복귀합니다.

| 동작화면
image

image

안드로이드에서도 잘 동작합니다.

image


| 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개의 좋아요

샘플 감사합니다. :grinning:
Loaded 에서 앱이 죽는지 확인 해봐야할 것 같네요.
OnAppearing event에서 ScanAndGetReuslt를하면 앱이 죽었거든요…

2개의 좋아요

관련해서 좀 더 부연 설명 하자면,

_ = 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개의 좋아요

SampleTest

GitHub Sample

에뮬레이터가 안돌아가서;;; 오래걸렸네요

1개의 좋아요

위에 훌륭한 내용으로 해결이 된듯 한데

위 질문의 답변과는 별개로 위 인용 글과 관련해서 WPF 환경에서

새 윈도우가 Open될때 데이터를 비동기적으로 처리하는 방법에 대해 정리했던 글을 공유해 봅니다.

5개의 좋아요

근본적으로 constructor에서 해당 잡을 실행하지 않으면 됩니다.

ViewModel에 Scan을 시작하는 비동기 메서드를 만들어두고 OnAppering 이벤트 받을 때 Scan하는 것도 방법이에요.
왜냐하면 Constructor는 객체가 살아있는 동안 한 번만 호출할텐데 WiFi Scan의 경우 한 번만 호출하지는 않을 것 같거든요.

2개의 좋아요

덧붙여서 WiFi 객체를 ViewModel에서 관리하는 것은 비용이 많이 들기 때문에 SSID, MAC ADDR과 같은 필요한 정보만 ViewModel에서 가지고 있는 것이 좋을 것 같아요.

2개의 좋아요