2015-11-05 77 views
2

使用Xamarin.Forms(對於iOS)我嘗試實現功能,以等待用戶確認已設置GeoLocation權限,然後再繼續。C#AutoResetEvent沒有發佈

我試圖做到這一點的方法是讓線程等待,直到使用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.AuthorizationChangedmanager.Failed事件似乎永遠不會被解僱,因此當狀態爲未確定時線程永遠不會釋放。

任何幫助或指針,不勝感激。

回答

2

沒有a good, minimal, complete code example可靠地再現問題,不可能知道問題是什麼。但是你的代碼肯定有一個明顯的設計缺陷,我希望解決這個缺陷將解決你的問題。

有什麼缺點?你正在等着什麼東西。你寫了一個方法,顯然應該代表異步操作—它有名稱中的「異步」,並返回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值,就像以前一樣。請注意,在這種情況下,如果您希望最後的Console.WriteLine()以正確的順序執行,即在操作實際完成後,您必須在方法中明確呼叫ContinueWith()
  • 你的代碼也有可能是一個可能的錯誤。也就是說,如果系統版本符合您的預期,您只能有條件地撥打RequestWhenInUseAuthorization()。這可能導致您正在嘗試修復的行爲,假設RequestWhenInUseAuthorization()方法是最終導致這些事件的任何提出。如果你不調用這個方法,那麼顯然這兩個事件都不會被提出,並且你的代碼將永遠等待。我將await與方法調用本身一起移動到相同的if子句中,並簡單地將結果設置爲子句中的false。我在這裏沒有足夠的背景知道所有這些應該如何工作;我假設你對你使用的API有足夠的熟悉,如果我的假設不正確,你可以解決任何剩餘的細節。最後,我想強調的是,假設你的問題實際上是阻止當前線程阻止了你試圖完成的異步操作,那麼從這個方法中移除等待是必要的,但這還不夠。您必須確保呼叫鏈中沒有任何內容等待返回的Task<bool>,否則阻止線程。我上面提到了這一點,但確保這一點非常關鍵,而且您沒有提供完整的代碼示例,因此我不能確保這種情況,所以我想確保這個非常重要的一點不被忽視。
+0

是的,代碼根本沒有任何意義 - 尤其是,考慮到'RequestWhenInUseAuthorization'必須在UI線程上調用。 所以他應該等待(隱式)在UI線程上的用戶輸入;例如。點擊允許或取消。 – nullpotent

+0

嘿,謝謝你的時間和詳細的答案。我學到了很多 !我注意到的一件事是AutoResetEvent不再使用。我嘗試在'C#等待事件完成'之後使用它。如果你不介意我再問一個問題,'result = await tcs.Task;'在這個上下文中是否做了類似於AutoResetEvent的事情,或者是否應該使用AutoResetEvent來防止多線程在使用? (請注意,我仍然是C#中的新手) – RVandersteen

+0

@RVandersteen:_「does'result = await tcs.Task;'在此上下文中執行與AutoResetEvent類似的操作」 - - 取決於您認爲的「相似」 。它在類似的意義上是,該方法中的代碼暫時「暫停」,直到事件發出信號。但是''AutoResetEvent''(ARE)通過使_thread_本身「暫停」來實現這一點,而使用'await'則不會。 –