C#AutoResetEvent没有释放

时间:2015-11-05 19:56:57

标签: c# ios xamarin autoresetevent

使用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.AuthorizationChangedmanager.Failed事件似乎永远不会被触发,因此当状态未确定时,线程永远不会释放。

非常感谢任何帮助或指示。

1 个答案:

答案 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>或者阻塞线程。我在上面提到了这一点,但它确保非常重要,而且你没有提供完整的代码示例,所以无法确保它能够确保它完整。是这样的,所以我想确保这个非常重要的一点不被忽视。