4

我有一个 Xamarin.Forms 应用程序,在我的 App 类中有这段代码(是的,这只是演示问题的示例):

    public App()
    {
        BlobCache.ApplicationName = "MyApp";
        BlobCache.EnsureInitialized();


        // The root page of your application
        MainPage = GetMainPage();
    }

    public object BlockingGetExternalUser()
    {
        return GetExternalUser().Result;
    }

    private async Task<object> GetExternalUser()
    {
        try
        {
            return await BlobCache.LocalMachine.GetObject<object>("user");
        }
        catch (KeyNotFoundException)
        {
            return null;
        }
    }

密钥“用户”不存在,所以我希望得到一个 KeyNotFoundException。但是我从来没有看到这个异常被抛出。相反,它只是“挂起”并且永远不会从 await GetObject 调用返回。

我正在使用 Android 5.0 的手机上运行它。

任何想法如何解决这一问题?我在做一些根本错误的事情吗?

更新:附带说明:可以尝试检查密钥是否确实存在于缓存中,然后才从缓存中检索它,而不是立即尝试 GetObject。但是,如果我没记错的话,除了调用 GetObject 并捕获上面示例中的异常之外,没有其他方法可以进行检查。对于只想知道某个项目是否存在的情况,这似乎并不理想。也许在 Akavache 中使用“Exists()”方法会很不错?或者,也许我错过了什么?

Update2:将示例更改为不在构造函数中使用异步方法。只是为了证明这不是问题所在。

Update3:从构造函数中删除调用。当我从代码中的任何位置调用 BlockingGetExternalUser 时,等待仍会挂起。

4

2 回答 2

6

你肯定有一个死锁。引用同步等待异步操作,为什么 Wait() 会在此处冻结程序

异步方法中的 await 正在尝试返回 UI 线程。

由于 UI 线程正忙于等待整个任务完成,因此您有 > 死锁。

请注意,您的呼叫.Result暗示Task.Wait()某处。

有两种解决方案:要么完全避免使用这些async方法,要么将代码包装成Task.Run这样:

public object BlockingGetExternalUser()
{
    return Task.Run<object>(() => GetExternalUser().Result);
}

(我希望它正在编译我没有在 VS 中验证 :)

根据经验,这些天我倾向于避免async与 SQLite 结合使用的方法。原因是大多数 SQLite 包装器库使用Task.Run反模式来async为其方法提供包装器,而 SQLite 没有任何异步的内在符号。请注意,尽管您可以将事物包装起来Task.Run以使其异步,而且这对于库设计者来说只是一种反模式,向他们的用户建议方法是异步的,而实际上它们不是异步的。您可以在此处阅读有关此内容的更多信息:Task.Run 作为反模式?

于 2015-07-16T16:40:54.200 回答
1

在构造函数 () 中使用异步方法var externalUser = GetExternalUser().Result;被视为错误代码。您不应该在类构造函数中使用异步方法。阅读本文:构造函数可以是异步的吗?

您可以尝试更改它以避免死锁:

Func<Task> task = async () => { await GetExternalUser().ConfigureAwait(false); };
task().Wait();

...但我不会推荐它。

于 2015-07-15T15:58:53.770 回答