【问题标题】:TaskCompletionSource SynchronizationContextTaskCompletionSource SynchronizationContext
【发布时间】:2017-06-10 11:18:31
【问题描述】:

我正在 Xamarin iOS 应用程序中编写一些基本的 Firebase 代码,并且遇到了 TaskCompletionSource 的经典死锁情况。

public Task<string> GetUsers()
{
    var tcs = new TaskCompletionSource<string>();
    _instance.GetChild("users").ObserveSingleEvent(DataEventType.Value,
        x => { tcs.SetResult(x); });
    return tcs.Task;
}

当我像这样阻止这段代码时:

var users = GetUsers().Result;

应用程序死锁。

如果我理解正确,回调会尝试在 .Result 等待的同一上下文中运行。

我不明白的是,如果我修改代码以等待 Task 中的 GetUsers() 调用,如下所示:

var result = Task.Run(
    async () => await AppContext.Database.GetUsers().ConfigureAwait(false)
).Result;

它仍然死锁。

第二种情况是怎么回事?由于Task.Run,代码在另一个线程上运行的事实不应该意味着外部.Result不会阻止回调调用吗?

编辑:

跟进 Nkosi 的评论,我之所以问这个问题,是因为我很好奇为什么代码会阻塞。如果我等待电话

var users = await GetUsers().ConfigureAwait(false);

然后僵局就会消失。我只是想了解为什么它在包裹在Task 中时会阻塞,因为根据我对Task.Run 的(显然不正确的)理解,它不应该。

【问题讨论】:

  • 对我来说这似乎是一个 XY 问题。在minimal reproducible example 中显示您最终要实现的目标,并且可能是可以解决的解决方案。它看起来像是异步调用和阻塞调用的混合。所以上面的调用堆栈也应该显示出来。
  • @Nkosi 我实际上并没有被卡住,因为如果我只是做var users = await Getusers().ConfigureAwait(false) 那么代码运行时不会出现死锁。我只是好奇为什么Task.Run 版本不起作用,因为根据我的理解它应该。
  • 好的。我对你的问题的误解。你熟悉 Stephen Cleary 的这篇文章吗msdn.microsoft.com/en-us/magazine/jj991977.aspx
  • @Nkosi 另一个 SO 问题真的很有趣——他描述的“线程池黑客”正是我 认为 我正在做的事情:将异步操作放在另一个线程。开始怀疑这是否与 Xamarin 相关... 将更深入地研究他在该问题 (msdn.microsoft.com/en-us/magazine/mt238404.aspx) 中引用的文章,看看我是否能在其中找到答案。

标签: c# firebase asynchronous xamarin.ios synchronizationcontext


【解决方案1】:

ObserveSingleEvent 总是将回调调度到 UI 线程(我认为所有或几乎所有 firebase 回调都会这样做)。它不会捕获同步上下文或类似的东西 - 只是总是将回调调度到 UI 线程(请记住 - 它只是原生 IOS 代码的包装器)。因此,当您通过等待Result 来阻塞您的 UI 线程时,无论您从哪个线程调用GetUsers,它都会由于明显的原因而死锁。您提到的链接描述了调用代码捕获当前同步上下文时的另一种情况,因此他们从没有同步上下文的后台线程调用该代码,并且不会向其发布回调。这里不是这样。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-12-07
    • 1970-01-01
    相关资源
    最近更新 更多