Task.Wait() Deadlock

chrismiszturchrismisztur USMember
edited May 2015 in Xamarin.iOS

I am having a hard time figuring out how to run async code from a sync context. At the moment this is necessary since I will be calling this CLR from Lua.

I've been searching and came upon this, some of this, then this, and this.

Basically my use case is spinning up CLLocationManager and waiting for the newest location.

Waiting for a location update from async code works fine:

await Services.Location.Location (true);

this always deadlocks:

Services.Location.LocationSync();

The end goal is to retrieve my location from Lua:

luanet.load_assembly('System')
Console=luanet.import_type('System.Console')

function Execute(self,param2)
    local location = param2.Services.Location:LocationSync();
    Console.WriteLine ('l ' .. location.Coordinates.Longitude:ToString());
end

Here is my location service:

public class LocationService: IServiceProvider
    {
        public virtual void OnServiceAdded ()
        {
            initialize ();
            start ();
        }

        public virtual void OnServiceRemoved ()
        {
            stop ();
        }

        public virtual void OnServiceAddAttempt ()
        {

        }

        private bool _isRunning = false;

        private CLLocationManager _lm;

        private CLLocation _lastKnownLocation;
        private TaskCompletionSource<CLLocation> _taskInitLocation;

        public CLLocation LocationSync()
        {
            Task
                .Run (async () => await this.Location (true))
                .Wait ();                                       //BUG: why does this deadlock


            return _lastKnownLocation;
        }


        public async Task<CLLocation> Location(bool autoStop = false)
        {
            if (!_isRunning)
                start ();

            Console.WriteLine("location requested");
            await _taskInitLocation.Task;
            Console.WriteLine("location retrieved");

            if (autoStop)
                stop ();

            return _lastKnownLocation;
        }


        private void initialize()
        {
            _lm = new CLLocationManager ();
            _lm.RequestWhenInUseAuthorization ();

            _lm.DistanceFilter = 50;
            _lm.DesiredAccuracy = CLLocation.AccuracyKilometer;

            _taskInitLocation = null;

            _lm.LocationsUpdated += (object sender, CLLocationsUpdatedEventArgs e) => {
                Console.WriteLine ("location update : " + e.Locations [e.Locations.Length - 1].ToString ());
                _lastKnownLocation = e.Locations [e.Locations.Length - 1];

                if (!_taskInitLocation.Task.IsCompleted) {
                    Console.WriteLine ("location lock ++");
                    _taskInitLocation.SetResult (_lastKnownLocation);
                }
            };
        }

        private void start()
        {
            if (_isRunning)
                return;

            _isRunning = true;

            _taskInitLocation = new TaskCompletionSource<CLLocation> ();

            if (CLLocationManager.LocationServicesEnabled)
                _lm.StartUpdatingLocation ();
        }

        private void stop()
        {
            if (!_isRunning)
                return;

            _isRunning = false;


            if (CLLocationManager.LocationServicesEnabled)
                _lm.StopUpdatingLocation ();
        }

        public void Start()
        {
            start ();
        }

        public void Stop()
        {
            stop ();
        }
    }

Thanks
/c

Posts

  • adamkempadamkemp USInsider, Developer Group Leader mod

    Using .Wait() or.Result is fraught with peril, and deadlocks are a common issue. The problem is that a lot of async tasks require doing things on the UI thread, and using Wait or Result from the UI thread will block the UI thread. That means you're waiting on a task that can't ever finish.

  • chrismiszturchrismisztur USMember

    Indeed @adamkemp

  • chrismiszturchrismisztur USMember

    So since everything is happening on Thread1, what are my options?

  • adamkempadamkemp USInsider, Developer Group Leader mod

    Don't block. Make an asynchronous API or make sure that code is called outside the UI thread.

  • chrismiszturchrismisztur USMember
    edited May 2015

    This works... for now, where LocationSync method is called from Lua.

    IEnumerator<CLLocation> _locationCoroutine()
            {
                CLLocation location = null;
    
                Task
                    .Run (async () => { await this.Location (true); } );
    
                while (location==null)
                {
                    location = _lastKnownLocation;
                    yield return null;
                }
    
                yield return location;
            }
    
            public CLLocation LocationSync()
            {
                var coroutine = _locationCoroutine ();
    
                while (coroutine.Current == null)
                {
                    coroutine.MoveNext();
                }
    
                var current = coroutine.Current;
    
                return current;
            }
    
  • adamkempadamkemp USInsider, Developer Group Leader mod

    I have no idea why that would work, which makes me very skeptical that it's a good solution.

  • chrismiszturchrismisztur USMember

    I totally agree.
    What paradigm should I look into to call outside of UI thread?
    I tried creating the CLLocationManager on a separate thread, but that didn't seem to work either.
    Even if I do block the UI thread, that's ok for now, but blocking the UI thread, blocks CLLocationManager.

  • adamkempadamkemp USInsider, Developer Group Leader mod

    Since you're calling the LocationSync method from Lua, and presumably you need that to be synchronous (is there no callback mechanism you can use?), then you will need the call from Lua to come from a different thread. That may mean invoking the Lua code form a background thread (in the C# side) so it all runs outside the UI thread, or it might mean using some Lua mechanism to run code in another thread (is that possible?).

    Once you get LocationSync off the main thread then you can run pieces of code on the UI thread if needed (like to get CLLocationManager to work), but if you block the UI thread then it makes things like this very difficult.

  • chrismiszturchrismisztur USMember

    I take that back. The coroutine way only worked in simulator, not on device.

  • adamkempadamkemp USInsider, Developer Group Leader mod

    Did the simulator work all along, though?

  • chrismiszturchrismisztur USMember

    No. Below deadlocks on simulator.

    Task
                    .Run (async () => await this.Location (true))
                    .Wait (); 
    
  • adamkempadamkemp USInsider, Developer Group Leader mod

    Well I still don't understand why your coroutine fixed anything then. That doesn't make sense.

Sign In or Register to comment.