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는 인자가 없고 반환 값이 없는 메서드를 호출하는 데 사용되는 델리게이트로 해당 형태의 메서드 호출에 최적화 되어 있습니다.
  • ActionMethodInvoker의 성능 차이는 미미합니다.
    • 둘 다 델리게이트 타입이기 때문에 기본적인 동작 방식은 유사합니다.
  • 다음의의 코드로 성능 차이를 간단히 비교해 볼 수 있습니다.
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();
    }
}