异步方法冻结UI

时间:2018-05-02 11:48:10

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

预期结果

UI线程调用

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)
{
    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呢?

感谢。

3 个答案:

答案 0 :(得分:3)

  

并且工作线程执行LongTask。

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

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

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

答案 1 :(得分:0)

主要规则是:

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

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

尝试创建扩展AsyncTask类的TestAsync类。 然后覆盖doInBackground,onPostExecute方法。

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

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

答案 2 :(得分:0)

任务一:async / await

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

<强>解释

当按钮单击事件委托被调用时,它将从主调度程序(UI线程)调用。它告诉委托同步调用TestAsync("Fab")。当委托运行测试异步方法时,会告诉它运行任务LongTask,但您也要告诉它等待该任务的结果,使用&#39; await&#39;请求。因此,在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绑定的工作移动到   后台线程,但后台线程对进程没有帮助   只是等待结果可用。

我希望这个答案会增加一些细节,以便与你问题的其他回答一起使用。