2015-11-05 81 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'则不会。 –