source

WPF 및 Winforms의 UI 스레드에 있는지 여부를 감지하는 중

manysource 2023. 5. 25. 22:10

WPF 및 Winforms의 UI 스레드에 있는지 여부를 감지하는 중

나는 주장 방법인 "보장"을 작성했습니다.아래의 현재 OnUiThread()는 현재 스레드가 UI 스레드인지 확인합니다.

  • 이것이 Winforms UI 스레드를 탐지하는 데 신뢰할 수 있습니까?
  • 우리 앱은 WPF와 Winforms가 혼합되어 있습니다. 유효한 WPF UI 스레드를 감지하는 가장 좋은 방법은 무엇입니까?
  • 이것을 하는 더 좋은 방법이 있습니까?아마도 코드 계약?

Ensure.cs

using System.Diagnostics;
using System.Windows.Forms;

public static class Ensure
{
    [Conditional("DEBUG")]
    public static void CurrentlyOnUiThread()
    {
        if (!Application.MessageLoop)
        {
            throw new ThreadStateException("Assertion failed: not on the UI thread");
        }
    }
}

사용 안 함

if(Dispatcher.CurrentDispatcher.Thread == Thread.CurrentThread)
{
   // Do something
}

Dispatcher.CurrentDispatcher현재 스레드에 디스패처가 없는 경우 새 스레드를 만들고 반환합니다.Dispatcher현재 스레드와 연결되어 있습니다.

대신 이렇게 해주세요.

Dispatcher dispatcher = Dispatcher.FromThread(Thread.CurrentThread);
if (dispatcher != null)
{
   // We know the thread have a dispatcher that we can use.
}

올바른 디스패처를 가지고 있거나 올바른 스레드에 있는지 확인하려면 다음 옵션을 사용합니다.

Dispatcher _myDispatcher;

public void UnknownThreadCalling()
{
    if (_myDispatcher.CheckAccess())
    {
        // Calling thread is associated with the Dispatcher
    }

    try
    {
        _myDispatcher.VerifyAccess();

        // Calling thread is associated with the Dispatcher
    }
    catch (InvalidOperationException)
    {
        // Thread can't use dispatcher
    }
}

CheckAccess()그리고.VerifyAccess()지능에 나타나지 않습니다.

또한, 만약 당신이 이런 종류의 것들에 의지해야 한다면 그것은 나쁜 디자인 때문일 가능성이 있습니다.프로그램에서 어떤 스레드가 어떤 코드를 실행하는지 알아야 합니다.

WPF의 경우 다음을 사용합니다.

public static void InvokeIfNecessary (Action action)
{
    if (Thread.CurrentThread == Application.Current.Dispatcher.Thread)
        action ();
    else {
        Application.Current.Dispatcher.Invoke(action);
    }
}

열쇠는 디스패치를 선택하는 대신에 있습니다.CurrentDispatcher(현재 스레드에 대한 디스패처 제공)는 현재 스레드가 응용 프로그램의 디스패처 또는 다른 컨트롤과 일치하는지 확인해야 합니다.

WinForms 내에서 일반적으로 사용할 수 있는 기능

if(control.InvokeRequired) 
{
 // Do non UI thread stuff
}

WPF용

if (!control.Dispatcher.CheckAccess())
{
  // Do non UI Thread stuff
}

나는 아마도 제네릭 제약 조건을 사용하여 이들 중 어느 것을 호출해야 하는지 결정하는 작은 방법을 쓸 것입니다.

public static bool CurrentlyOnUiThread<T>(T control)
{ 
   if(T is System.Windows.Forms.Control)
   {
      System.Windows.Forms.Control c = control as System.Windows.Forms.Control;
      return !c.InvokeRequired;
   }
   else if(T is System.Windows.Controls.Control)
   {
      System.Windows.Controls.Control c = control as System.Windows.Control.Control;
      return c.Dispatcher.CheckAccess()
   }
}

WPF의 경우:

// You are on WPF UI thread!
if (Thread.CurrentThread == System.Windows.Threading.Dispatcher.CurrentDispatcher.Thread)

WinForms의 경우:

// You are NOT on WinForms UI thread for this control!
if (someControlOrWindow.InvokeRequired)

아마도요.Control.InvokeRequired(WinForms) 및Dispatcher.CheckAccess(WPF) 괜찮으세요?

당신은 당신의 UI에 대한 지식을 당신의 논리에 밀어넣고 있습니다.이것은 좋은 디자인이 아닙니다.

UI 스레드가 남용되지 않도록 하는 것은 UI의 권한 내에 있으므로 UI 계층에서 스레드를 처리해야 합니다.

또한 Winform 및 Dispatcher에서 IsInvokeRequired를 사용할 수 있습니다.WPF에서 호출... 그리고 동기식 및 비동기식 asp.net 요청 내에서도 코드를 사용할 수 있습니다...

실제로 애플리케이션 로직 내에서 낮은 수준에서 스레드를 처리하려고 하면 불필요한 복잡성이 증가하는 경우가 많습니다.사실, 실질적으로 전체 프레임워크는 이 점이 인정된 상태로 작성됩니다. 프레임워크의 거의 모든 것이 스레드 세이프입니다.스레드 안전을 보장하는 것은 (더 높은 수준의) 발신자에게 달려 있습니다.

다음은 WPF에서 UI 속성(INotify를 구현하는) 수정 시도를 탐지하는 데 사용하는 코드의 일부입니다.UI가 아닌 스레드에서 변경된 속성):

    public event PropertyChangedEventHandler PropertyChanged;

    private void NotifyPropertyChanged(String info)
    {
        // Uncomment this to catch attempts to modify UI properties from a non-UI thread
        //bool oopsie = false;
        //if (Thread.CurrentThread != Application.Current.Dispatcher.Thread)
        //{
        //    oopsie = true; // place to set a breakpt
        //}

        if (PropertyChanged != null)
        {
            PropertyChanged(this, new PropertyChangedEventArgs(info));
        }
    }

WPF의 경우:

내 스레드의 Dispatcher가 실제로 시작되었는지 확인해야 합니다. " 클래스", "WPF 클래스"를 절대로 입니다. 설령 당신이 절대로 하지 않더라도.Dispatcher.Run()저는 결국 반성하게 되었습니다.

public static class WpfDispatcherUtils
{
    private static readonly Type dispatcherType = typeof(Dispatcher);
    private static readonly FieldInfo frameDepthField = dispatcherType.GetField("_frameDepth", BindingFlags.Instance | BindingFlags.NonPublic);

    public static bool IsInsideDispatcher()
    {
        // get dispatcher for current thread
        Dispatcher currentThreadDispatcher = Dispatcher.FromThread(Thread.CurrentThread);

        if (currentThreadDispatcher == null)
        {
            // no dispatcher for current thread, we're definitely outside
            return false;
        }

        // get current dispatcher frame depth
        int currentFrameDepth = (int) frameDepthField.GetValue(currentThreadDispatcher);

        return currentFrameDepth != 0;
    }
}

다음과 같은 스레드 ID를 비교할 수 있습니다.

       var managedThreadId = System.Windows.Threading.Dispatcher.FromThread(System.Threading.Thread.CurrentThread)?.Thread.ManagedThreadId;
        var dispatcherManagedThreadId = System.Windows.Application.Current.Dispatcher.Thread.ManagedThreadId;
        if (managedThreadId == dispatcherManagedThreadId)
        {
             //works in ui dispatcher thread
        }

MVVM을 사용하는 것은 실제로 매우 쉽습니다.View ModelBase(모델 베이스 보기)에 다음과 같은 것을 참조하십시오.

protected readonly SynchronizationContext SyncContext = SynchronizationContext.Current;

아니면...

protected readonly TaskScheduler Scheduler = TaskScheduler.Current; 

그런 다음 특정 View Model이 "관측 가능한" 항목을 터치해야 할 경우 컨텍스트를 확인하고 그에 따라 대응할 수 있습니다.

public void RefreshData(object state = null /* for direct calls */)
{
    if (SyncContext != SynchronizationContext.Current)
    {
        SyncContext.Post(RefreshData, null); // SendOrPostCallback
        return;
    }
    // ...
}

또는 컨텍스트로 돌아가기 전에 백그라운드에서 다른 작업을 수행합니다.

public void RefreshData()
{
    Task<MyData>.Factory.StartNew(() => GetData())
        .ContinueWith(t => {/* Do something with t.Result */}, Scheduler);
}

일반적으로 MVVM(또는 다른 아키텍처)을 순서대로 따르면 UI 동기화에 대한 책임이 어디에 있는지 쉽게 알 수 있습니다.그러나 기본적으로 이 작업을 어디서든 수행하여 개체가 생성되는 컨텍스트로 돌아갈 수 있습니다.크고 복잡한 시스템에서 이를 깨끗하고 일관되게 처리할 수 있는 '가드'를 만드는 것이 쉬울 것이라고 확신합니다.

당신의 유일한 책임은 원래의 상황으로 돌아가는 것이라고 말하는 것이 타당하다고 생각합니다.동일한 작업을 수행하는 것은 고객의 책임입니다.

WPF의 경우:

다음은 상위 답변을 기반으로 한 스니펫으로, 매우 일반적이라는 의미의 대리자를 사용합니다.

        /// <summary>
        /// Invokes the Delegate directly on the main UI thread, based on the calling threads' <see cref="Dispatcher"/>.
        /// NOTE this is a blocking call.
        /// </summary>
        /// <param name="method">Method to invoke on the Main ui thread</param>
        /// <param name="args">Argumens to pass to the method</param>
        /// <returns>The return object of the called object, which can be null.</returns>
        private object InvokeForUiIfNeeded(Delegate method, params object[] args)
        {
            if (method == null) throw new ArgumentNullException(nameof(method));

            var dispatcher = Application.Current.Dispatcher;

            if (dispatcher.Thread != Thread.CurrentThread)
            {
                // We're on some other thread, Invoke it directly on the main ui thread.
                return dispatcher.Invoke(method, args);
            }
            else
            {
                // We're on the dispatchers' thread, which (in wpf) is the main UI thread.
                // We can safely update ui here, and not going through the dispatcher which safes some (minor) overhead.
                return method.DynamicInvoke(args);
            }

        }

        /// <inheritdoc cref="InvokeForUiIfNeeded(Delegate, object[])"/>
        public TReturn InvokeForUiIfNeeded<TReturn>(Delegate method, params object[] args)
            => (TReturn) InvokeForUiIfNeeded(method, args);

두 번째 방법은 더 많은 유형의 안전 반환 유형을 허용합니다.또한 자동으로 작업을 수행하는 오버로드를 추가했습니다.Func그리고.Action코드의 변수( " 코드의매개변예수내예")

        /// <inheritdoc cref="InvokeForUiIfNeeded(System.Delegate, object[])"/>
        private void InvokeForUiIfNeeded(Action action)
            => InvokeForUiIfNeeded((Delegate) action);

고참; 더더Func그리고.Action에서물은에서 Delegate그냥 캐스팅할 수 있습니다.

또한 작업을 수행하는 자체 일반 오버로드를 추가할 수도 있습니다. 오버로드를 많이 만들지는 않았지만 확실히 예를 들어, 예를 들어,

        /// <inheritdoc cref="InvokeForUiIfNeeded(System.Delegate, object[])"/>
        private void InvokeForUiIfNeeded<T1>(Action<T1> action, T1 p1)
            => InvokeForUiIfNeeded((Delegate)action, p1);

        /// <inheritdoc cref="InvokeForUiIfNeeded(System.Delegate, object[])"/>
        private TReturn InvokeForUiIfNeeded<T1, TReturn>(Func<T1, TReturn> action, T1 p1)
            => (TReturn)InvokeForUiIfNeeded((Delegate)action, p1);
Thread.CurrentThread.ManagedThreadId == Dispatcher.Thread.ManagedThreadId

이것을 확인하는 더 좋은 방법이 있습니까?

언급URL : https://stackoverflow.com/questions/5143599/detecting-whether-on-ui-thread-in-wpf-and-winforms