SafeInvoke
UI Update
앱을 만드는 경우 쓰레드에서 UI 에 접근하는 경우가 생기므로 다음과 같은 코드가 종종 쓰입니다.
if (dgv.InvokeRequired)
dgv.Invoke(new Action(() => dgv.Rows.Clear()));
else
dgv.Rows.Clear();
호출 스레드가 UI 스레드인지 확인한 후 올바른 스레드에서 메서드를 호출하기 위한 코드이며
- 만약 현재 스레드가 UI 스레드가 아니라면,
Invoke
메서드를 사용하여dgv.Rows.Clear()
를 UI 스레드에서 실행하고 - 현재 스레드가 UI 스레드라면, 바로
dgv.Rows.Clear()
실행. 이라는 의미입니다. 이런 코드 사용이 빈번하다면 다음과 같이 함수로 만들 수 있다.
SafeInvoke with Action
public static void SafeInvoke(this Control control, Action action)
{
if (control == null) throw new ArgumentNullException(nameof(control));
if (action == null) throw new ArgumentNullException(nameof(action));
if (control.InvokeRequired)
{
if (control.IsHandleCreated && !control.IsDisposed)
{
try
{
control.Invoke(action);
}
catch (ObjectDisposedException) { /* 무시 */}
catch (InvalidOperationException) { /* 무시 */ }
catch (Exception ex)
{
//Logging 이나 에러처리
}
}
}
else
action();
}
사용은 다음과 같습니다.
dgv.SafeInvoke(() => dgv.Rows.Clear());
SafeInvoke with MethodInvoker
괄호가 많아서 별로라면, 조금 더 다듬어 볼 수 있습니다.
public static void SafeInvoke(this Control control, MethodInvoker method)
{
if (control == null) throw new ArgumentNullException(nameof(control));
if (method == null) throw new ArgumentNullException(nameof(method));
if (control.InvokeRequired)
{
if (control.IsHandleCreated && !control.IsDisposed)
{
try
{
control.Invoke(method);
}
catch (ObjectDisposedException)
{
// 컨트롤이 dispose된 경우 무시
}
catch (InvalidOperationException)
{
// 컨트롤 핸들이 유효하지 않은 경우 무시
}
catch (Exception ex)
{
//Logging 이나 에러처리
}
}
}
else
{
method();
}
}
사용은 다음과 같습니다.
- 인자와 반환값이 없는 함수 호출 시에는
dgv.SafeInvoke(dgv.Rows.Clear);
- 그 외에는
dgv.SafeInvoke(() => dgv.Enabled = true);
- 물론 인자와 반환값이 없는 함수도 동일하게 사용 가능합니다.
추가
MethodInvoker
는 인자가 없고 반환 값이 없는 메서드를 호출하는 데 사용되는 델리게이트로 해당 형태의 메서드 호출에 최적화 되어 있습니다.Action
과MethodInvoker
의 성능 차이는 미미합니다.- 둘 다 델리게이트 타입이기 때문에 기본적인 동작 방식은 유사합니다.
- 다음의의 코드로 성능 차이를 간단히 비교해 볼 수 있습니다.
using System;
using System.Diagnostics;
using System.Windows.Forms;
public class PerformanceTest
{
public static void Main()
{
Control control = new Control();
control.CreateControl();
Stopwatch stopwatch = new Stopwatch();
// Measure performance for Action
stopwatch.Start();
for (int i = 0; i < 1000000; i++)
SafeInvokeAction(control, () => { });
stopwatch.Stop();
Console.WriteLine($"Action elapsed time: {stopwatch.ElapsedMilliseconds} ms");
// Measure performance for MethodInvoker
stopwatch.Restart();
for (int i = 0; i < 1000000; i++)
SafeInvokeMethodInvoker(control, delegate { });
stopwatch.Stop();
Console.WriteLine($"MethodInvoker elapsed time: {stopwatch.ElapsedMilliseconds} ms");
}
public static void SafeInvokeAction(Control control, Action action)
{
if (control.InvokeRequired)
control.Invoke(action);
else
action();
}
public static void SafeInvokeMethodInvoker(Control control, MethodInvoker method)
{
if (control.InvokeRequired)
control.Invoke(method);
else
method();
}
}