使用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 ();
您可以在下面找到完整的课程:
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
事件似乎永远不会被触发,因此当状态未确定时,线程永远不会释放。
非常感谢任何帮助或指示。
答案 0 :(得分:2)
如果没有a good, minimal, complete code example可靠地再现问题,就无法确定问题是什么。但是你的代码肯定有一个明显的设计缺陷,我希望解决这个缺陷能解决你的问题。
缺陷是什么?您正在等待任何。你编写了一个显然应该表示异步操作的方法 - 它具有&#34; Async&#34;在名称中返回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()
。 1}}的异步操作,返回其结果。
这将允许该方法立即返回,即使在您调用Task<bool>
的情况下,也不会更改调用者的语义(即它仍然看到await
返回值并且可以{{1 }} 结果)。如果方法同步完成,则调用者不必实际等待。如果它没有,那么假设调用者已被正确写入并且它本身没有阻塞等待结果的线程,则操作将能够正常完成,设置完成源的结果并让等待它的代码继续进行。
注意:
async
/ await
功能。如果不这样做,那就很容易调整以适应;它基本上和上面的技术基本相同,只是你实际上将TaskCompletionSource
用于方法中的所有分支而不仅仅是异步分支,然后返回tcs.Task
价值就像你以前一样。请注意,在这种情况下,如果您希望最后ContinueWith()
以正确的顺序执行,即在操作实际完成后,您必须在方法中显式调用Console.WriteLine()
。RequestWhenInUseAuthorization()
。假设RequestWhenInUseAuthorization()
方法最终导致其中任何一个事件被引发,此也可能导致您尝试修复的行为。如果你没有调用该方法,那么显然不会引发任何事件,并且你的代码将永远等待。我使用方法调用本身将await
移动到相同的if
子句中,并在false
子句中将结果简单地设置为else
。我在这里没有足够的背景知道这一切应该如何运作;我假设您已经足够熟悉您正在使用的API,如果我的假设不正确,您可以解决任何剩余的详细信息。Task<bool>
或者阻塞线程。我在上面提到了这一点,但它确保非常重要,而且你没有提供完整的代码示例,所以我无法确保它能够确保它完整。是这样的,所以我想确保这个非常重要的一点不被忽视。