티스토리 뷰
UI Freeze은 화면이 사용자의 입력에 반응하지 않고, 멈추는 현상을 이야기 합니다. 옛날에는 이 문제를 해결하기 위한 여러가지 꼼수를 사용해야 했지만, 이제는 async/await를 이용하면 쉽게 해결할 수 있습니다.
다들 알고 계신가요? 흐흐흐
우선 이 현상이 발생되는 예제를 만들어 보도록 하겠습니다.
1. MainWindow.xaml (UI Freeze 발생)
CategoryComboBox1, CategoryListBox1, CategoryComboBox2, CategoryListBox2 이렇게 4개의 컨트롤을 배치하고, 각 컨트롤의 SelectionChanged 이벤트를 연결했습니다.
<StackPanel>
<TextBlock Text="Category1"/>
<ComboBox x:Name="CategoryComboBox1" SelectionChanged="CategoryComboBox1_SelectionChanged">
<ComboBoxItem Content="0_Category_1"/>
<ComboBoxItem Content="0_Category_2"/>
<ComboBoxItem Content="0_Category_3"/>
<ComboBoxItem Content="0_Category_4"/>
<ComboBoxItem Content="0_Category_5"/>
</ComboBox>
</StackPanel>
<ListBox x:Name="CategoryListBox1" Grid.Row="1"
SelectionChanged="CategoryListBox1_SelectionChanged">
</ListBox>
<StackPanel Grid.Column="1">
<TextBlock Text="Category2"/>
<ComboBox x:Name="CategoryComboBox2" SelectionChanged="CategoryComboBox2_SelectionChanged"/>
</StackPanel>
<ListBox x:Name="CategoryListBox2" Grid.Column="1" Grid.Row="1"
SelectionChanged="CategoryListBox2_SelectionChanged"/>
콤보박스나, 리스트박스의 SelectionChanged 이벤트가 발생되면, GetDatas 메소드를 호출해서 데이터를 조회합니다.
물론 진짜 데이터를 조회하는 것은 아니지만요~
조회된 데이터를 ItemsSource에 넣어고, SelectedIndex에 0을 넣어서 첫번째 아이템이 선택되도록 합니다. 이렇게하면, 다른 컨트롤의 SelectionChanged 이벤트가 다시 발생하여 연쇄반응을 일으키게 됩니다.
SelectionChanged이벤트는 ItemsSource가 변경될 때와 SelectedItem이 변경될 때 발생합니다. 그래서, Control의 SelectedItem이 null인지 확인하는 코드를 추가했습니다.
private void CategoryComboBox1_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
if(CategoryComboBox1.SelectedItem == null)
{
return;
}
BusyPopup.IsOpen = true;
var datas = GetDatas(_sleepConst,
((ComboBoxItem)CategoryComboBox1.SelectedItem).Content as string ?? "0_Category_0");
CategoryListBox1.ItemsSource = datas;
CategoryListBox1.SelectedIndex = 0;
}
private void CategoryListBox1_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
if(CategoryListBox1.SelectedItem == null)
{
return;
}
BusyPopup.IsOpen = true;
var datas = GetDatas(_sleepConst, CategoryListBox1.SelectedItem as string ?? "CategoryListBox1");
CategoryComboBox2.ItemsSource = datas;
CategoryComboBox2.SelectedIndex = 0;
}
GetDatas메소드는 Task.Delay()를 이용해서 강제 지연을 발생 시킵니다. 반환되는 데이터는 selectedString과 연결되어있습니다.
private IList<string> GetDatas(int sleep, string selectedString)
{
Task.Delay(sleep).Wait();
_count++;
var words = selectedString.Split('_');
return new List<string>
{
$"{_count}_{words[1] + words[2]}_01",
$"{_count}_{words[1] + words[2]}_02",
$"{_count}_{words[1] + words[2]}_03",
$"{_count}_{words[1] + words[2]}_04",
$"{_count}_{words[1] + words[2]}_05",
$"{_count}_{words[1] + words[2]}_06",
$"{_count}_{words[1] + words[2]}_07",
$"{_count}_{words[1] + words[2]}_08",
$"{_count}_{words[1] + words[2]}_09",
$"{_count}_{words[1] + words[2]}_10",
};
}
실행하면 어떻게 될까요?
따란~ 15초간 아무것도 보이지도, 사용자 입력도 불가능한 상태를 지속하다가...
한번에 모든 데이터가 표시됩니다. 여기서, 콤보박스나 리스트박스의 아이템을 하나 변경하면, 또 다시 UI Freeze가 발생하고, 시간이 지나면 화면이 갑자기 갱신됩니다.
옛날 옛날에는 이렇게 되는 것을 막아 볼려고 별짓을 다 했었는데... 물론 지금도 괴로워하는 개발자가... 있겠죠?
2. SecondWindow.xaml (async/await 버전)
애플리케이션 실행을 SecondWindow.xaml로 변경하기 위해서는 App.xaml.cs에 StartupUri를 수정합니다.
public partial class App : Application
{
public App()
{
//StartupUri = new Uri("/MainWindow.xaml", UriKind.RelativeOrAbsolute);
StartupUri = new Uri("/SecondWindow.xaml", UriKind.RelativeOrAbsolute);
}
}
GetDatasAsync 메소드에서는 await Task.Delay(sleep);를 이용해서 강제로 대기시간을 발생시키고 있습니다.
나머지는 위에서 사용한 GetDatas 메소드와 동일합니다.
HttpClient나 서비스 등의 호출 후 대기하는 부분을 async/await로 만들어서 활용하시면 됩니다.
private async Task<IList<string>> GetDatasAsync(int sleep, string selectedString)
{
await Task.Delay(sleep);
_count++;
var words = selectedString.Split('_');
return new List<string>
{
$"{_count}_{words[1] + words[2]}_01",
$"{_count}_{words[1] + words[2]}_02",
$"{_count}_{words[1] + words[2]}_03",
$"{_count}_{words[1] + words[2]}_04",
$"{_count}_{words[1] + words[2]}_05",
$"{_count}_{words[1] + words[2]}_06",
$"{_count}_{words[1] + words[2]}_07",
$"{_count}_{words[1] + words[2]}_08",
$"{_count}_{words[1] + words[2]}_09",
$"{_count}_{words[1] + words[2]}_10",
};
}
SelectionChanged 이벤트가 발생할 때마다, GetDatasAsync 메소드를 await 키워드를 추가해서 호출해주기만 하면됩니다. 참 쉽죠?
private async void CategoryComboBox1_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (CategoryComboBox1.SelectedItem == null)
{
return;
}
BusyPopup.IsOpen = true;
var datas = await GetDatasAsync(_sleepConst, ((ComboBoxItem)CategoryComboBox1.SelectedItem).Content as string ?? "0_Category_0");
CategoryListBox1.ItemsSource = datas;
CategoryListBox1.SelectedIndex = 0;
}
private async void CategoryListBox1_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (CategoryListBox1.SelectedItem == null)
{
return;
}
BusyPopup.IsOpen = true;
var datas = await GetDatasAsync(_sleepConst, CategoryListBox1.SelectedItem as string ?? "CategoryListBox1");
CategoryComboBox2.ItemsSource = datas;
CategoryComboBox2.SelectedIndex = 0;
}
실행을 해보겠습니다.
첫 실행화면에서 아까는 볼 수 없었던 Busy 컨트롤이 보입니다.
리스트박스와 콤보박스가 시간이 지나면서 채워지는 것을 바로바로 확인할 수 있습니다. 물론 Busy 컨트롤은 계속 떠있는 중이구요
모든 데이터를 출력한 후에는 Busy 컨트롤이 숨겨집니다.
이렇게 간단하게 async/await를 이용하도록 메소드를 변경하면, UI Freeze 문제를 쉽게 해결할 수 있습니다.
이 방법은 WPF, WinForm 모두 사용할 수 있으니 꼭 활용하시면 좋겠습니다.
Async/await 프로그래밍에 대한 더 자세한 사항은 여기를 참고하시기 바랍니다.
3. 소스
WpfTest/EventAsyncSample at master · kaki104/WpfTest (github.com)
'WPF .NET' 카테고리의 다른 글
대용량 데이터처리시 발생되는 UI Freeze 문제 해결 part2 (0) | 2022.04.12 |
---|---|
대용량 데이터처리시 발생되는 UI Freeze 문제 해결 part1 (0) | 2022.04.11 |
.NET 5 Application 버전(빌드 일시) 생성하고 사용하기 (0) | 2022.03.25 |
[기초] ComboBox, ListBox 중요 프로퍼티 사용법 part3 (0) | 2022.03.21 |
[기초] ComboBox, ListBox 중요 프로퍼티 사용법 part2 (0) | 2022.03.15 |
- Total
- Today
- Yesterday
- Cross-platform
- Visual Studio 2022
- kiosk
- .net 5.0
- Build 2016
- XAML
- uno-platform
- #prism
- ef core
- visual studio 2019
- IOT
- Behavior
- LINQ
- MVVM
- Always Encrypted
- UWP
- windows 11
- #MVVM
- Microsoft
- #uwp
- ComboBox
- uno platform
- #Windows Template Studio
- C#
- WPF
- dotNETconf
- Windows 10
- .net
- PRISM
- Bot Framework
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | ||
6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 | 14 | 15 | 16 | 17 | 18 | 19 |
20 | 21 | 22 | 23 | 24 | 25 | 26 |
27 | 28 | 29 | 30 | 31 |