使用 Xamarin.Forms(适用于 iOS),我尝试实现在继续之前等待用户确认已设置 GeoLocation 权限的功能。
我尝试实现这一点的方法是让线程等待直到使用 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 ();
您可以在下面找到完整的类(class):
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("ermission 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 事件似乎永远不会被触发,因此当状态未确定时线程永远不会释放。
非常感谢任何帮助或指示。
Best Answer-推荐答案 strong>
无 a good, minimal, complete code example可靠地重现问题,不可能确切地知道问题是什么。但是你的代码肯定有一个明显的设计缺陷,我希望解决这个缺陷会解决你的问题。
有什么缺陷?你正在等待任何事情。你写了一个显然应该代表异步操作的方法——它的名称中有“Async”并返回一个 Task<bool> 而不是 bool - 然后你以这样的方式编写方法,Task<bool> 无论采用哪个代码路径,返回的内容都将始终完成。
为什么这如此糟糕?好吧,除了它完全无法利用您正在实现的接口(interface)的异步方面这一简单事实之外,很可能 CLLocationManager 您正在使用的类希望能够在您的 IsGeolocationEnabledAsync() 所在的同一线程中运行方法被调用。由于此方法在引发事件之前不会返回,并且由于在方法返回之前无法引发事件,因此您会遇到死锁。
恕我直言,这就是您的类(class)应该如何实现:
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("ermission 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;
}
}
IE。把方法变成 async 方法,不要理会TaskCompletionSource 除非你真的需要等待任何东西,然后await CLLocationManager 的结果的异步操作,返回其结果。
即使在您调用 CLLocationManager.RequestWhenInUseAuthorization() 的情况下,这也将允许该方法立即返回。 ,而不改变调用者的语义(即它仍然看到 Task<bool> 返回值并且可以 await 结果)。如果该方法同步完成,则调用者实际上不必等待。如果没有,那么假设调用者已经正确写入并且它本身没有阻塞等待结果的线程,操作将能够正常完成,设置完成源的结果并让等待它的代码继续进行.
笔记: 以上假设在您的平台中您可以使用 async /await 特征。如果你不这样做,调整以适应是很简单的;它与上面的技术基本相同,只是您实际上会使用 TaskCompletionSource 对于方法中的所有分支,而不仅仅是异步分支,然后将返回 tcs.Task 值(value)就像你以前一样。请注意,在这种情况下,您必须调用 ContinueWith() 如果您想要最后一个 Console.WriteLine() ,请在您的方法中明确说明以正确的顺序执行,即在操作实际完成之后。 您的代码也有似乎是一个可能的错误。也就是你只拨打RequestWhenInUseAuthorization() 有条件地,如果系统版本符合您的预期。这也可能导致您尝试修复的行为,假设 RequestWhenInUseAuthorization() 方法是最终导致这些事件中的任何一个引发的原因。如果您不调用该方法,那么显然不会引发任何事件,并且您的代码将永远等待。我搬家了await 进同if 子句与方法调用本身,并简单地将结果设置为 false 在 else 条款。我没有足够的上下文来确定这一切应该如何工作;我假设您对正在使用的 API 足够熟悉,如果我的假设不正确,您可以解决任何剩余的细节问题。 最后,我想强调的是,假设您的问题实际上是阻塞当前线程是阻止您尝试完成异步操作的原因,那么从此方法中删除等待是必要的,但还不够。你必须确保调用链中没有任何东西在等待返回的 Task<bool> 以及或以其他方式阻塞线程。我在上面提到了这一点,但确保这一点非常重要,而且您没有提供完整的代码示例,因此我无法确保情况如此,因此我想确保这一非常重要的点不会被忽视。
关于C# AutoResetEvent 不释放,我们在Stack Overflow上找到一个类似的问题:
https://stackoverflow.com/questions/33553640/
|