【问题标题】:async method freezes UI异步方法冻结 UI
【发布时间】:2018-05-02 11:48:10
【问题描述】:

预期结果

TestAsync被UI线程调用,工作线程执行LongTask

实际结果

ui线程执行一切

测试

public override View OnCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
{
    // [...]

    _fab = root.FindViewById<FloatingActionButton>(...);
    _fab.Click += ((sender, v) =>  TestAsync("fab"));

    // [...]
}

private async void TestAsync(string origin)
{
    await LongTask(); 
}

private async Task LongTask()
{
    while (true) { } // Thread should hung here
}

结果:UI 冻结。

测试 2: 为了确保 UI 正在执行一切,我做了一个网络操作(在 Android 的 UI 线程中是不允许的)

public async Task<int> Network(string s)
{
    URL url = new URL("http://www.randomtext.me/api/");
    Java.IO.BufferedReader reader = new Java.IO.BufferedReader(new Java.IO.InputStreamReader(url.OpenStream()));

    int count = 0;
    string str;
    while ((str = reader.ReadLine()) != null) {
        count += str.Length;
    }
    reader.Close();

    await Task.Delay(3000); // To make sure this method is compiled as async even though it isn't necessary

    return count;
}

结果:NetworkOnMainThreadException.

问题:

为什么LongTaskNetwork 方法不在工作线程中执行?那么await/async 是什么?

谢谢。

【问题讨论】:

  • 异步方法中的上下文切换(可能)发生在第一个 await。它可能根本不会发生,但在第一次 await 之前,一切肯定会在您调用它的同一线程上运行。
  • 好吧,我无法在 cmets 中正确解释它,除此之外,这个网站上已经解释了数百次。例如,将您的第二种方法更改为使用while ((str = await reader.ReadLineAsync()) != null)
  • 您可以从中提取的主要内容是:如果您对await 没有任何东西,请不要使用async。它并非旨在将您的工作转移到后台线程,因为您有 Task.Run 和类似的构造。
  • 是的,在这种情况下使用reader.ReadLineAsync 是不够的,因为url.OpenStream 会抛出异常并且它没有等效的异步版本。由于我上面描述的原因,它抛出了这个异常——在第一次等待之前,你肯定在 UI 线程上(你可能也在 UI 线程上)。因此,不要使用 java 类 - 使用 .NET 类,例如 HttpWebRequestHttpClient 等。或者使用 android 特定的后台任务。如果您想从 DB 中读取数据 - 只需这样做即可进行测试。
  • 作为指南 - 它只是 async\await 工作原理的核心,您可以阅读任何相关指南。例如文档:docs.microsoft.com/en-us/dotnet/csharp/async。在这个网站上也有很多关于 async\await 不同方面的不同问题。

标签: c# android xamarin xamarin.android async-await


【解决方案1】:

一个工作线程执行 LongTask。

不,这不会自行发生。您从 GUI 线程等待,因此您将阻止它。这种模式适用于异步 I/O,因为这将释放线程。

但是当你的情况是 CPU 受限时,没有用 async/await,使用 Task.Run:

private void TestAsync(string origin)
{
    Task.Run( LongTask); 
}

【讨论】:

  • 你说:You await from the GUI thread and so you will block it。我不确定这是不是真的,文档 (Asynchronous programming with async and await) 说:When you use asynchronous methods, the application continues to respond to the UI.
  • This pattern is OK for async I/O 。我的Network 方法可以做到这一点,但在主线程上执行。您能否提供更准确的示例来说明何时创建新线程?我的目标是了解 await 何时在新线程中执行,因为我发现在 UI 中进行了一些数据库读取,这就是我编写测试的原因。我的实际代码从数据库中读取并用它更新 UI。
【解决方案2】:

主要规则是:

  • ui相关行为必须在主线程(ui线程)中
  • 网络/繁重任务必须在非主 (ui) 线程中执行。

您尝试从主 (ui) 线程执行网络任务,导致出现此异常。

尝试创建扩展 AsyncTask 类的 TestAsync 类。 然后重写 doInBackground、onPostExecute 方法。

  • 在 doInBackground 方法中创建您的网络逻辑。
  • 然后在 onPostExecute 方法中更新您的 UI。

参考:http://programmerguru.com/android-tutorial/what-is-asynctask-in-android/

【讨论】:

  • 我不是在寻找解决方案。我的问题是:为什么 LongTaskNetworking 不在工作人员中执行?
  • 如果你对扩展 AsyncTask 类不感兴趣,那么潜在的问题就在这里:“await LongTask();”。它阻塞了你的 ui 线程。查看更多信息:stackoverflow.com/questions/13636648/…
【解决方案3】:

任务一:异步/等待

private async void TestAsync(string origin)
{
    await LongTask(); 
}

说明:

当按钮单击事件委托被调用时,它是从主调度程序(UI 线程)调用的。它告诉代理同步调用TestAsync("Fab")。当委托通过测试异步方法运行时,它被告知运行任务LongTask,但您也告诉它通过使用“等待”请求来等待该任务的结果。所以TestAsync方法在LongTask完成之前无法完成;因为这是从主调度程序请求的,所以 UI 的其余部分会挂起,直到完成。

分辨率:

public override View OnCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
{
    // [...]

    _fab = root.FindViewById<FloatingActionButton>(...);
    _fab.Click += ((sender, v) =>  TestAsync("fab"));

    // [...]
}

private async void TestAsync(string origin)
{
    // By not calling await you tell the code that you don't need this
    // calling method to await a return value.
    // using Task.Run explicitly requests another thread process this request
    Task.Run(LongTask); 
}

private async Task LongTask()
{
    while (true) { } // Thread should hung here
}


任务二:网络

public async Task<int> Network(string s)
{
    URL url = new URL("http://www.randomtext.me/api/");
    Java.IO.BufferedReader reader = new Java.IO.BufferedReader(new Java.IO.InputStreamReader(url.OpenStream()));

    int count = 0;
    string str;
    while ((str = reader.ReadLine()) != null) {
        count += str.Length;
    }
    reader.Close();

    await Task.Delay(3000); // To make sure this method is compiled as async even though it isn't necessary

    return count;
}

说明:

和以前一样,这在很大程度上取决于任务的启动方式,请参阅 Microsoft 文档中的以下引用:

async 和 await 关键字不会导致额外的线程 创建的。异步方法不需要多线程,因为异步 方法不在自己的线程上运行。该方法在当前运行 同步上下文并仅在线程上使用时间 方法处于活动状态。您可以使用 Task.Run 将 CPU 密集型工作移动到 后台线程,但后台线程对进程没有帮助 那只是在等待结果可用。

我希望这个答案可以添加更多细节,以便与您的问题的其他回答一起使用。

【讨论】:

    猜你喜欢
    • 2021-12-24
    • 1970-01-01
    • 1970-01-01
    • 2015-09-11
    • 2015-06-07
    • 2014-08-08
    • 2016-05-12
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多