ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • How to do async calls without locking the UI thread in WPF
    IT Story/C# & WPF 2019. 11. 27. 18:29
    반응형

     

    WPF developers that have worked with async await have most likely run into problems with avoiding race conditions with the UI thread, either making the entire UI lock up or burden the UI thread and cause clients not responding. This article will show you how to avoid this. The way to do this is to await using another thread and afterwards use that result back again on the WPF thread to do the updates in the UI. We use ThreadPool.QueueUserWorkItem for this. As you can see, we user an inner method marked with async keyword to await the results. This is done to avoid the classic "async proliferation" seen in async code, where the async keyword spreads upwards to all methods. We instead use an outher method and call the async method and use the Result of the Task returned. We could do some quality checking here, to check if the Task succeeded of course. The Task object contains status information about if the results are really available or not and Result will throw an exception if there was an error in the retrieval of the async results from the lower layers of the software app layers. Example code:

     

    DispatcherUtil.AsyncWorkAndUiThreadUpdate(Dispatcher.CurrentDispatcher, () => GetSomeItems(someId),
     x => GetSomeItemsUpdateUIAfterwards(x), displayWaitCursor:true);
    //Note here that we retrieve the data not on the UI thread, but on a dedicated thread and after retrieved the
    //result, we do an update in the GUI. 
    private List<SomeItemDataContract> GetSomeItems(int someId)
            {
             var retrieveTask = GomeSomeItemsInnerAsync(someId);
             return retrieveTask.Result;
            }
     
    private async Task<List<SomeItemDataContract>> GetSomeItemsInnerAsync(int someId)
            {
             List<SomeItemDataContract> retrieveTask = await SomeServiceAgent.GetSomeItems(someId);
             return retrieveTask;
            }
    
    private void GetSomeItemsUpdateUIAfterwards(SomeItemDataContract x){
     if (x != null){
      //Do some UI stuff - remember RaisePropertyChanged
     }
    }
    public static void AsyncWorkAndUiThreadUpdate<T>(Dispatcher currentDispatcher, Func<T> threadWork, Action<T> guiUpdate, 
    bool displayWaitCursor = false)
            {
             if (displayWaitCursor)
              PublishMouseCursorEvent<T>(Cursors.Wait);
    
             // ReSharper disable once UnusedAnonymousMethodSignature 
             ThreadPool.QueueUserWorkItem(delegate(object state)
                {
                  T resultAfterThreadWork = threadWork();
                  // ReSharper disable once UnusedAnonymousMethodSignature
                  currentDispatcher.BeginInvoke(DispatcherPriority.Normal, new Action<T>(delegate {
           
                  if (displayWaitCursor)
                   PublishMouseCursorEvent<T>(Cursors.Arrow);
     
                  guiUpdate(resultAfterThreadWork);
               }), resultAfterThreadWork);
    
                });
     
            }

    The PublishMouseCursorEvent publishes a prism event that is captured by a Bootstrapper class, but what you choose to do here is of course up to you. One way is to subscribe such an event (either a CompositePresentationEvent as in Prism or an ordinary CLR event for example):

    반응형

    댓글

Designed by Tistory.