Async with a part in the UI

I have an async task, within this task, add items to the UI launched via Dispatcher.BeginInvoke where I update the ObservebleCollection. For streaming safe access to the collection, I use semaphoreSlim, but since the build request continues on the UI thread, and Dispatcher.BeginInvoke is also running on the UI thread, I get a deadlock.

    private readonly ObservebleCollection<String> parameters = new ObservebleCollection<String>();
    private readonly SemaphoreSlim semaphore = new SemaphoreSlim(0, 1);
    //Called from UI
    public ObservebleCollection<String> Parameters
    {
        get
        {
          semaphore.Wait();
          var result = this.parameters;
          semaphore.Release();

          return result;
        }
    }

    public async Task Operation()
    {
            await semaphore.WaitAsync();
            List<String> stored = new List<String>();
            foreach (var parameter in currentRobot.GetParametersProvider().GetParameters())
            {
                stored.Add(parameter.PropertyName);
            }

            //Can't do add all items in UI at once, because it take a long time, and ui started lag
            foreach (var model in stored)
            {
                await UIDispatcher.BeginInvoke(new Action(() =>
                {
                    this.parameters.Add(model);
                }), System.Windows.Threading.DispatcherPriority.Background);
            }
            semaphore.Release();
        }

      

And how I got the deadlock: When I click a button in my program, the operation is performed. When I click another button, the program will try to access the Parameters property. And I got a dead lock = D

Problem: In an async operation, I populate the checkblecollection via Dispatcher.BeginInvoke for each item separately, because if I add all items at once using the Dispatcher, the UI will lag behind. So I need a sync method to access the Parameters property that will wait for the operation to complete.

+3


source to share


1 answer


await

provides code when it runs in its original sync context, in this case on the UI thread. This makes it unnecessary BeginInvoke

since the code is already working on the correct flow.

As a result, you are trying to acquire two locks on the same object from the same thread, resulting in a deadlock.

If you need thread-safe access to a collection of objects, avoid manually creating locks and use a thread-safe collection such as ConcurrentQueue or ConcurrentDictionary.

Also, I can't say I understand what the code is trying to do as it doesn't do anything in the background or asynchronously. It could be a simple way that copies parameters from one collection to another, and it will still be thread safe if written correctly. You could just write:

var _parameters=new ConcurrentQueue<string>();

      

....

public void CopyParameters()
{
     foreach (var parameter in currentRobot.GetParametersProvider().GetParameters())
     {
            _parameters.Enqueue(parameter.PropertyName);
     }
}

      

If you are using data binding in Parameters property, just raise PropertyChanged

after adding all records



What is the real problem you are trying to solve?

UPDATE

The real problem seems to be that the UI hangs if you try to add too many elements at the same time. It's not a threading issue, it's a WPF issue. There are various solutions, all of which involve promoting PropertyChanged

only after all properties have been added.

If you don't want the old values, just create a list with the new values, replace the old values, then raise the PropertyChanged event, like this:

private ObservebleCollection<String> _parameters = new ObservebleCollection<String>();

public ObservebleCollection<String> Parameters 
{
    get 
    { 
        return _parameters;
    }
    private set 
    {
        _parameters=value;
        PropertyChanged("Parameters");
    }

    public void CopyParameters()
    {
        var newParameters=currentRobot.GetParametersProvider()
                                      .GetParameters()
                                      .Select(p=>p.PropertyName);
        Parameters=new ObservableCollection<string>(newParameters);
    }

      

If you have code that changes Parameters

one element at a time, you can easily swap ObservableCollection

to any other type of collection, even an array string[]

.

Another option is to subclass ObservableCollection to add AddRange support as shown in this SO question

+3


source







All Articles