티스토리 뷰
과거의 경험한 내용 중에 대용량의 데이터를 파일이서 잃거나, Rest API를 통해서 전달 받아서, DataGrid나 ListBox에 출력시 UI Freeze 현상과 메모리 사용량이 증가하는 한다는 것을 해결해보려고 노력을 했던 적이 있습니다.
2022.04.04 - [WPF .NET] - Async/Await를 사용해서 UI Freeze 해결하기
이전 포스트의 async/await를 이용해도 발생하는 현상인데..
모든 데이터를 조회하고, 컨트롤에 출력하는 시간이 좀 걸리더라도 (화면에 프로그래스 출력하고 카운트 출력하면 그래도 기다릴 수 있을 것이라고 생각하면서..), UI가 부드럽게 동작할 수 있는 방법을 찾아보려고 합니다.
먼저, 대용량 파일을 불러오는 화면을 만들어 보겠습니다.
프로젝트에 여러개의 Window가 있으며, App.xaml.cs에서 시작 윈도우를 지정하는 코드가 있습니다. 참고하시기 바랍니다.
1. 대용량 데이터 파일 확보
Crimes - 2001 to Present | City of Chicago | Data Portal
시카고의 2001년 부터 지금까지의 범죄 데이터를 다운로드 받아서 사용해 보도록 하겠습니다.
음..데이터량이 상당한것 같습니다. 최종적으로 1.7GB정도의 csv파일이며, 총 7,514,910 라인입니다.
이 파일은 소스에 포함되지 않기 때문에 여러분이 가지고 계시는 큰 용량의 파일을 사용하시거나 직접 다운로드 받으시기 바랍니다.
2. SampleData.cs
다운로드 받은 시카고 범죄 현황 파일의 데이터를 모델로 변환할 22개의 프로퍼티를 가지고 있는 모델을 만들었습니다.
데이터를 읽어 들인 후 모델로 변환해서 컨트롤에 입력할 것입니다.
/// <summary>
/// Sample Data model
/// </summary>
public class SampleData
{
public string ID { get; set; }
public string CaseNumber { get; set; }
public string Date { get; set; }
public string Block { get; set; }
public string IUCR { get; set; }
public string PrimaryType { get; set; }
public string Description { get; set; }
public string LocationDescription { get; set; }
public string Arrest { get; set; }
public string Domestic { get; set; }
public string Beat { get; set; }
public string District { get; set; }
public string Ward { get; set; }
public string CommunityArea { get; set; }
public string FBICode { get; set; }
public string XCoordinate { get; set; }
public string YCoordinate { get; set; }
public string Year { get; set; }
public string UpdatedOn { get; set; }
public string Latitude { get; set; }
public string Longitude { get; set; }
public string Location { get; set; }
}
3. MainWindow.xaml
File Open 버튼을 클릭하면 Button_Click 이벤트를 호출하고, 이벤트 내부에서 파일을 선택하고 데이터를 불러올 예정입니다. ListBox와 DataGrid를 각각 추가하고, 모델의 프로퍼티 중 5개만 화면에 표시하도록 만들었습니다.
<Window x:Class="LargeFileReadSample.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:LargeFileReadSample"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<StackPanel Orientation="Horizontal">
<Button Content="File Open" Width="80" Click="Button_Click"/>
</StackPanel>
<ProgressBar Grid.Column="1" Grid.ColumnSpan="2" Margin="5,0" IsIndeterminate="True"/>
<ListBox Grid.Row="1" x:Name="listBox">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding ID}"/>
<TextBlock Text="{Binding CaseNumber}" Margin="5,0"/>
<TextBlock Text="{Binding Date}" Margin="5,0"/>
<TextBlock Text="{Binding Block}" Margin="5,0"/>
<TextBlock Text="{Binding IUCR}" Margin="5,0"/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<DataGrid Grid.Row="1" Grid.Column="1" x:Name="dataGrid">
<DataGrid.Columns>
<DataGridTextColumn Header="Id" Binding="{Binding ID}"/>
<DataGridTextColumn Header="CaseNumber" Binding="{Binding CaseNumber}"/>
<DataGridTextColumn Header="Date" Binding="{Binding Date}"/>
<DataGridTextColumn Header="Block" Binding="{Binding Block}"/>
<DataGridTextColumn Header="IUCR" Binding="{Binding IUCR}"/>
</DataGrid.Columns>
</DataGrid>
</Grid>
</Window>
4. MainWindow.xaml.cs
버튼 클릭시 발생하는 Button_Click이벤트에서는 OpenFileDialog()를 이용해서 파일을 선택하고, 선택된 파일명이 없으면 종료하도록 만들었습니다.
private void Button_Click(object sender, RoutedEventArgs e)
{
var dialog = new OpenFileDialog
{
Filter = "All files (*.*)|*.*"
};
var result = dialog.ShowDialog();
if (result == false)
{
return;
}
var fileName = dialog.FileName;
if (string.IsNullOrEmpty(fileName))
{
return;
}
}
GetModels 메서드는 파일명을 받아서, 스트림을 열고, 한줄씩 읽어, SampleModel로 변환 후 List에 넣고, 모든 데이터를 읽어서 처리하면, 최종 List를 반환하고 있습니다.
한번에 읽어 오는 ReadToEnd()는 System.OutOfMemoryException에러가 발생됩니다.
private SampleData GetModelFromString(string item)
{
var columns = item.Split(",");
var model = new SampleData();
var propertys = model.GetType().GetProperties();
for (int i = 0; i < propertys.Count(); i++)
{
var property = propertys[i];
property.SetValue(model, columns[i], null);
}
return model;
}
private IList<SampleData> GetModels(string fileName)
{
bool isFirstLine = true;
IList<SampleData> returnValues = new List<SampleData>();
using (var reader = new StreamReader(fileName))
{
while (!reader.EndOfStream)
{
var line = reader.ReadLine();
if (isFirstLine == false && line != null)
{
var model = GetModelFromString(line);
returnValues.Add(model);
}
else
{
isFirstLine = false;
}
}
reader.Close();
}
return returnValues;
}
위의 GetModels를 적용한 Button_Click 메서드 입니다.
private void Button_Click(object sender, RoutedEventArgs e)
{
var dialog = new OpenFileDialog
{
Filter = "All files (*.*)|*.*"
};
var result = dialog.ShowDialog();
if (result == false)
{
return;
}
var fileName = dialog.FileName;
if (string.IsNullOrEmpty(fileName))
{
return;
}
var models = GetModels(fileName);
listBox.ItemsSource = models;
Debug.WriteLine($"models count : {models.Count}");
}
5. 첫번째 실행
화면에 프로그래스바 컨트롤을 넣어서 UI Freeze 형상이 발생하는지 여부를 확인합니다.
File Open 버튼 클릭 후 파일 선택
UI Freeze가 발생해서 윈도우 화면 캡처 기능을 이용해서 캡처 했습니다.
오픈이 완료된 화면
CPU : 8%
Memory : 8.7GB
UI Freeze : 약 1분
DataGrid까지 출력하면 너무 오래걸려서 제외 했습니다.
성능 개선을 위해서, 어떤 방법을 사용해야 할까요?
6. Yield를 아시나요?
yield return 문을 사용하여 각 요소를 따로따로 반환할 수 있습니다.
Microsoft Doc의 Yield 설명을 참고하면, 스트림 중간에 결과 중 일부를 반환할 수 있는 기능이라는 것입니다.
GetModels에서 파일 데이터를 읽은 후 결과를 returnValues에 저장하고 있다가, 작업이 완료되면 결과를 리턴시키고, 그 데이터를 출력했지만, yield return을 사용하면, 한줄 읽고 반환, 한줄 읽고 반환이 가능하다는 것입니다.
시작 윈도우를 YieldWindow.xaml로 변경합니다.
public partial class App : Application
{
public App()
{
//StartupUri = new Uri("/MainWindow.xaml", UriKind.RelativeOrAbsolute);
StartupUri = new Uri("/YieldWindow.xaml", UriKind.RelativeOrAbsolute);
}
}
YieldWindow.xaml.cs
Yield를 사용하는 경우 리턴타입은 IEnumerable<T>로 고정되며, 메서드 내부에 데이터를 모아 놓기 위한 리스트가 없고, 한줄읽고, 모델로 변환 후 바로 리턴시켜 버립니다.
private IEnumerable<SampleData> GetModels(string fileName)
{
bool isFirstLine = true;
using (var reader = new StreamReader(fileName))
{
while (!reader.EndOfStream)
{
var line = reader.ReadLine();
if (isFirstLine == false && line != null)
{
var model = GetModelFromString(line);
yield return model;
}
else
{
isFirstLine = false;
}
}
reader.Close();
}
}
Button_Click 수정
var models = GetModels(fileName);
listBox.ItemsSource = models;
7. 두번째 실행
File Open 버튼 클릭 후 파일 선택 후 UI Freeze 발생
오픈이 완료된 화면
CPU : 8%
Memory : 8.7GB
UI Freeze : 약 1분
결과를 한번에 반환하는 방법과 한줄씩 결과를 반환하는 방법에 현재까지 차이는 없는 것 같습니다.
하지만, 조금더 개선을 할 수 있을 것 같은 느낌같은 느낌이 들고 있습니다. Part2에서 계속 진행하겠습니다.
'WPF .NET' 카테고리의 다른 글
대용량 데이터처리시 발생되는 UI Freeze 문제 해결 part3 - MVVM (0) | 2022.04.13 |
---|---|
대용량 데이터처리시 발생되는 UI Freeze 문제 해결 part2 (0) | 2022.04.12 |
Async/Await를 사용해서 UI Freeze 해결하기 (0) | 2022.04.04 |
.NET 5 Application 버전(빌드 일시) 생성하고 사용하기 (0) | 2022.03.25 |
[기초] ComboBox, ListBox 중요 프로퍼티 사용법 part3 (0) | 2022.03.21 |
- Total
- Today
- Yesterday
- WPF
- Bot Framework
- UWP
- visual studio 2019
- ComboBox
- Cross-platform
- ef core
- Behavior
- Visual Studio 2022
- Build 2016
- uno platform
- Microsoft
- PRISM
- kiosk
- dotNETconf
- Always Encrypted
- XAML
- MVVM
- IOT
- Windows 10
- LINQ
- windows 11
- .net
- #prism
- .net 5.0
- #uwp
- uno-platform
- #MVVM
- C#
- #Windows Template Studio
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |