2015-11-05 2 views
2

Использование Xamarin.Forms (для iOS) Я пытаюсь реализовать функциональность, чтобы дождаться подтверждения пользователем разрешения на геолокацию, которое должно быть установлено до продолжения.C# AutoResetEvent не освобождает

Способ, которым я пытаюсь достичь этого, заключается в том, что Thread продолжает до тех пор, пока событие не будет запущено с использованием AutoResetEvent.

Основная проблема (я верю) находится в следующем коде:

manager.AuthorizationChanged += (object sender, CLAuthorizationChangedEventArgs args) => { 

    Console.WriteLine ("Authorization changed to: {0}", args.Status); 

    if (UIDevice.CurrentDevice.CheckSystemVersion (8, 0)) { 
     tcs.SetResult (args.Status == CLAuthorizationStatus.AuthorizedAlways || args.Status == CLAuthorizationStatus.AuthorizedWhenInUse); 
    } else { 
     tcs.SetResult (args.Status == CLAuthorizationStatus.Authorized); 
    } 

    _waitHandle.Set(); 
}; 

manager.Failed += (object sender, Foundation.NSErrorEventArgs e) => { 

    Console.WriteLine ("Authorization failed"); 

    tcs.SetResult (false); 

    _waitHandle.Set(); 
}; 

if (UIDevice.CurrentDevice.CheckSystemVersion (8, 0)) { 
    manager.RequestWhenInUseAuthorization(); 
} 

_waitHandle.WaitOne(); 

Вы можете найти полный класс ниже:

public class LocationManager : ILocationManager 
{ 
    static EventWaitHandle _waitHandle = new AutoResetEvent (false); 

    private TaskCompletionSource<bool> tcs = new TaskCompletionSource<bool>(); 

    public LocationManager() 
    { 
    } 

    public Task<bool> IsGeolocationEnabledAsync() 
    { 
     Console.WriteLine (String.Format("Avaible on device: {0}", CLLocationManager.LocationServicesEnabled)); 
     Console.WriteLine (String.Format("Permission on device: {0}", CLLocationManager.Status)); 

     if (!CLLocationManager.LocationServicesEnabled) { 
      tcs.SetResult (false); 
     } else if (CLLocationManager.Status == CLAuthorizationStatus.Denied || CLLocationManager.Status == CLAuthorizationStatus.Restricted) { 
      tcs.SetResult (false); 
     } else if (CLLocationManager.Status == CLAuthorizationStatus.NotDetermined) { 

      Console.WriteLine ("Waiting for authorisation"); 

      CLLocationManager manager = new CLLocationManager(); 

      manager.AuthorizationChanged += (object sender, CLAuthorizationChangedEventArgs args) => { 

       Console.WriteLine ("Authorization changed to: {0}", args.Status); 

       if (UIDevice.CurrentDevice.CheckSystemVersion (8, 0)) { 
        tcs.SetResult (args.Status == CLAuthorizationStatus.AuthorizedAlways || args.Status == CLAuthorizationStatus.AuthorizedWhenInUse); 
       } else { 
        tcs.SetResult (args.Status == CLAuthorizationStatus.Authorized); 
       } 

       _waitHandle.Set(); 
      }; 

      manager.Failed += (object sender, Foundation.NSErrorEventArgs e) => { 

       Console.WriteLine ("Authorization failed"); 

       tcs.SetResult (false); 

       _waitHandle.Set(); 
      }; 

      if (UIDevice.CurrentDevice.CheckSystemVersion (8, 0)) { 
       manager.RequestWhenInUseAuthorization(); 
      } 

      _waitHandle.WaitOne(); 

      Console.WriteLine (String.Format ("Auth complete: {0}", tcs.Task.Result)); 

     } else { 
      if (UIDevice.CurrentDevice.CheckSystemVersion (8, 0)) { 
       tcs.SetResult (CLLocationManager.Status == CLAuthorizationStatus.AuthorizedAlways || CLLocationManager.Status == CLAuthorizationStatus.AuthorizedWhenInUse); 
      } else { 
       tcs.SetResult (CLLocationManager.Status == CLAuthorizationStatus.Authorized); 
      } 
     } 

     return tcs.Task; 
    } 
} 

Он отлично работает за исключением того, что я не могу понять почему событие manager.AuthorizationChanged или manager.Failed, кажется, никогда не срабатывает, и, таким образом, нить никогда не будет выпущена, если статус не определен.

Любая помощь или указатели с благодарностью.

ответ

2

Без a good, minimal, complete code example, который надежно воспроизводит проблему, невозможно точно знать, в чем проблема. Но ваш код, безусловно, имеет вопиющий дефект дизайна, и я надеюсь, что устранение этого дефекта решит вашу проблему.

В чем проблема? Что вы ждете на ничего на всех. Вы написали метод, который, по-видимому, должен представлять асинхронную операцию —, имеет имя «Async» в имени и возвращает Task<bool> вместо bool —, а затем вы записываете метод таким образом, чтобы возвращаемый Task<bool> всегда был завершен, независимо от того, какой путь кода принят.

Почему это так плохо? Ну, помимо простого факта, что он полностью не использует асинхронный аспект интерфейса, который вы реализуете, вполне вероятно, что класс CLLocationManager, который вы используете, будет иметь возможность запускать в том же потоке, в котором ваш IsGeolocationEnabledAsync() называется метод. Поскольку этот метод не возвращается до тех пор, пока события не будут подняты, и поскольку события не могут быть подняты до тех пор, пока метод не вернется, у вас есть тупик.

ИМХО, это то, как должен быть реализован ваш класс:

public class LocationManager : ILocationManager 
{ 
    public async Task<bool> IsGeolocationEnabledAsync() 
    { 
     bool result; 

     Console.WriteLine (String.Format("Avaible on device: {0}", CLLocationManager.LocationServicesEnabled)); 
     Console.WriteLine (String.Format("Permission on device: {0}", CLLocationManager.Status)); 

     if (!CLLocationManager.LocationServicesEnabled) { 
      result = false; 
     } else if (CLLocationManager.Status == CLAuthorizationStatus.Denied || CLLocationManager.Status == CLAuthorizationStatus.Restricted) { 
      result = false; 
     } else if (CLLocationManager.Status == CLAuthorizationStatus.NotDetermined) { 
      TaskCompletionSource<bool> tcs = new TaskCompletionSource<bool>(); 

      Console.WriteLine ("Waiting for authorisation"); 

      CLLocationManager manager = new CLLocationManager(); 

      manager.AuthorizationChanged += (object sender, CLAuthorizationChangedEventArgs args) => { 

       Console.WriteLine ("Authorization changed to: {0}", args.Status); 

       if (UIDevice.CurrentDevice.CheckSystemVersion (8, 0)) { 
        tcs.SetResult (args.Status == CLAuthorizationStatus.AuthorizedAlways || args.Status == CLAuthorizationStatus.AuthorizedWhenInUse); 
       } else { 
        tcs.SetResult (args.Status == CLAuthorizationStatus.Authorized); 
       } 
      }; 

      manager.Failed += (object sender, Foundation.NSErrorEventArgs e) => { 

       Console.WriteLine ("Authorization failed"); 

       tcs.SetResult (false); 
      }; 

      if (UIDevice.CurrentDevice.CheckSystemVersion (8, 0)) { 
       manager.RequestWhenInUseAuthorization(); 
       result = await tcs.Task; 
      } else { 
       result = false; 
      } 

      Console.WriteLine (String.Format ("Auth complete: {0}", tcs.Task.Result)); 

     } else { 
      if (UIDevice.CurrentDevice.CheckSystemVersion (8, 0)) { 
       result = CLLocationManager.Status == CLAuthorizationStatus.AuthorizedAlways || CLLocationManager.Status == CLAuthorizationStatus.AuthorizedWhenInUse; 
      } else { 
       result = CLLocationManager.Status == CLAuthorizationStatus.Authorized; 
      } 
     } 

     return result; 
    } 
} 

Т.е. превратите метод в метод async, не беспокоитесь о TaskCompletionSource вообще, если вам действительно не нужно ждать чего-либо, а затем await результат асинхронной операции CLLocationManager, возвращая его результат.

Это позволит методу немедленно вернуться даже в том случае, когда вы звоните CLLocationManager.RequestWhenInUseAuthorization(), без изменения семантики для вызывающего абонента (т.е. он по-прежнему видит Task<bool> возвращаемого значение и может await результата). Если метод завершается синхронно, вызывающему абоненту не придется ждать. Если это не так, то, предполагая, что вызывающий был правильно написан, и сам он не блокирует поток, ожидающий результата, операция будет выполнена нормально, установка результата источника завершения и разрешение ожидающего его кода ,

Примечания:

  • выше предполагает, что в вашей платформе у вас имеется функция async/await. Если вы этого не сделаете, достаточно просто настроить его; это в основном тот же метод, что и выше, за исключением того, что вы фактически использовали бы TaskCompletionSource для всех ветвей в методе вместо асинхронного, а затем возвращали значение tcs.Task так же, как и раньше. Обратите внимание, что в этом случае вам нужно будет вызвать ContinueWith() явно в вашем методе, если вы хотите, чтобы последний Console.WriteLine() выполнялся в правильном порядке, то есть после завершения операции.
  • В вашем коде также есть то, что кажется возможной ошибкой. То есть, вы вызываете только RequestWhenInUseAuthorization(), если системная версия такая, как вы ожидаете. Это также могло привести к поведению, которое вы пытаетесь исправить, в предположении, что метод RequestWhenInUseAuthorization() - это то, что в конечном итоге приводит к поднятию любого из этих событий. Если вы не вызываете метод, то, очевидно, ни одно событие не будет поднято, и ваш код будет ждать всегда. Я переместил await в то же самое предложение if с вызовом метода и просто установил результат в false в предложении else. Мне не хватает контекста, чтобы точно знать, как все это должно работать; Я предполагаю, что у вас достаточно знания API-интерфейса, который вы используете, чтобы разрешить любые оставшиеся мелкие детали, если мои предположения не были правильными.
  • Наконец, я хочу подчеркнуть, что, предполагая, что ваша проблема в том, что блокирование текущего потока - это то, что мешает асинхронной операции, которую вы пытаетесь выполнить, что удаление ожидания из этого метода необходимо, но недостаточно. Вы должны ничего не делать в цепочке вызовов, ожидая возвращенного Task<bool>, или иначе блокируя поток. Я упомянул об этом выше, но это настолько важно для обеспечения, и вы не представили полный пример кода, так что нет никакого способа I может гарантировать, что это так, поэтому я хочу убедиться, что этот очень важный момент не упускается из виду.
+0

Yup, код не имеет никакого смысла - особенно, учитывая, что 'RequestWhenInUseAuthorization' должен быть вызван в потоке пользовательского интерфейса. Поэтому он должен ждать (неявно) для пользовательского ввода в потоке пользовательского интерфейса; например. нажмите «Разрешить» или «Отменить». – nullpotent

+0

Эй, спасибо за ваше время и подробный ответ. Я многому научился ! Одна вещь, которую я заметил, это то, что AutoResetEvent больше не используется. Я попытался использовать это после некоторых поисковых запросов для «C# ждать окончания события». Если вы не возражаете, чтобы я задал еще один вопрос, делает ли'result = wait tcs.Task? 'В этом контексте сделать что-то похожее на AutoResetEvent или использовать AutoResetEvent в случае запуска нескольких потоков? (Обратите внимание, что я все еще новичок в C#) – RVandersteen

+0

@RVandersteen: _ "does' result = wait tcs.Task; 'в этом контексте сделать что-то похожее на AutoResetEvent" _ - это зависит от того, что вы считаете "похожим" , Это похоже в том смысле, что код _in этого метода_ временно «приостанавливается» до тех пор, пока событие не будет сигнализировано. Но все равно, что «AutoResetEvent» (ARE) делает это, вызывая «приостановку» _thread_, тогда как использование 'await' не делает. –

Смежные вопросы