Android任务线程和UI

时间:2014-04-02 14:18:35

标签: c# android multithreading asynchronous

我正在尝试在后台/异步/ ...中运行大型XML文件的导入 - 换句话说,我试图将其从UI线程中删除。

我的代码如下:

private string ImportXML()
    {
        string returnFile = null;
        ParseXML.ParseXML parsexml = new ParseXML.ParseXML (chosenFile);

        ProgressDialog progress = new ProgressDialog(this);
        progress.SetMessage(GetString(Resource.String.ImportXML));
        progress.Indeterminate = true;
        progress.Show();

        Task xmlTask = new Task (() => returnFile = parsexml.ProcessXML());

        xmlTask.Start();

        while (!(xmlTask.IsCompleted || xmlTask.IsCanceled || xmlTask.IsFaulted))
            progress.Show();

        xmlTask.Wait(); 

        progress.Hide();

        return returnFile;
    } 

代码似乎同步运行或仍然在主UI线程中运行,因为UI更新(我暂时尝试使用while循环)没有发生 - 我没有达到看看进展情况。

操作可能需要几秒钟 - 我的一些示例文件最多可能需要10秒 - 因此我并不热衷于使用AsyncTask,因为我认为它不适合长时间操作。< / p>

有人可以告诉我,我在哪里出错吗?

2 个答案:

答案 0 :(得分:0)

我创建了一个示例AsyncTask,向您展示如何处理与ui线程分开的数据。此AsyncTask将从资源加载String并将其设置为TextView

重要提示:通常,您根本不需要AsyncTask从资源加载String。我刚刚选择了这个作为示例来展示如何在单独的线程中执行工作。

对于任何AsyncTask,只需记住一件事:您在一个单独的线程中执行工作,而不是在ui线程中。单独的线程完成后,无法保证您引用的ui元素仍然存在,甚至您的应用程序仍在运行。因此,如果您引用ActivityContext或某些ui元素(例如TextViewButton或任何其他复杂对象,当后台线程完成时可能不再存在你必须使用WeakReference。通过允许您引用的对象被垃圾回收,WeakReferences是一种解决此问题的聪明方法。在AsyncTask中,您可以检查您所需的对象是否仍然存在,如果是,则执行您的工作而不会造成内存泄漏。您可以像这样使用WeakReference

WeakReference<TextView> textViewReference = new WeakReference<TextView>(textView);

如您所见,WeakReference是一种通用类型。如果您想在这种情况下引用TextView,则必须在WeakReference之后写<TextView>告诉WeakReference。如果您引用Context,则会写WeakReference<Context>,或者如果您引用SQliteDatabase,则会写WeakReference<SQLiteDatabase>。我想你看到这里的模式了。
您将要引用的对象的实例传递给WeakReference的构造函数。这是上面示例的(textView)部分。如果您以后想要从WeakReference获取实例,您可以这样做:

TextView textView = textViewReference.get();

如果您引用的TextView不再存在,get()将只返回null。因此,要检查TextView是否仍然存在,您可以这样做:

TextView textView = textViewReference.get();
if(textView != null) {
   // TextView still exists. 
} else {
   // TextView doesn't exist anymore. Most likely he has already been garbage collected.
}

如果您理解这个关于如何以及何时使用WeakReferences的简单原则,那么实施AsyncTask应该没有问题。我已经解释了有关AsyncTask评论的其他重要内容,只需查看源代码即可。如果您有任何其他问题,请随时提出。

public class ExampleTask extends AsyncTask<Void, Void, String> {

    // We use WeakReferences for all complex objects to prevent memory leaks
    private final WeakReference<TextView> textViewRefernece;
    private final WeakReference<Context> contextReference;

    // Primitives are fine. WeakReferences are not needed here.
    private int resourceId;

    public ExampleTask(Context context, TextView textView, int resourceId) {

        // We create the WeakReferences to our Context and to the TextView in which we want the result to appear.
        this.contextReference = new WeakReference<Context>(context);
        this.textViewRefernece = new WeakReference<TextView>(textView);

        this.resourceId = resourceId;
    }

    @Override
    protected void onPreExecute() {
        super.onPreExecute();

        // This method is executed on the ui thread just before the background thread starts.
        // You can perform some inital setup here but mostly you can leave this empty
    }

    @Override
    protected String doInBackground(Void... params) {

        // This method is executed in a background thread. Here we can do our work.
        // First we have to get the context from the WeakReference
        Context context = contextReference.get();

        // Now we check if the Context still exists. If the context is null it has already been garbage collected.
        // That would happen for example if the app has been closed or even if it crashed.
        if(context != null) {

            // The context is not null which means it still exists we can continue and load the required String and return it
            return context.getString(this.resourceId);
        }

        // If something went wrong we return null.
        return null;
    }

    @Override
    protected void onPostExecute(String result) {
        super.onPostExecute(result);

        // This method is executed on the ui thread after the background thread is finished.
        // Here we get our final result and can set it to our TextView

        // First we check if the String result is null. If it is not null the background thread has finished successfully and we can continue.
        if(result != null) {

            // Here we get our TextView from the WeakReference
            TextView textView = textViewRefernece.get();

            // Now we check if the TextView still exists. If it is null it has already been garbage collected.
            // That would happen for example if the app has been closed while the AsyncTask has been running.
            // Or simply if the Fragment has been replaced or the Activity has changed.
            if(textView != null) {

                // The TextView is not null which means it still exists. We can now set our text.
                textView.setText(result);
            }
        }
    }
}

答案 1 :(得分:-1)

是的,您创建了一个在非UI线程中完成某项工作的任务。然后你告诉UI线程坐在那里等待其他任务完成。在该任务完成之前,您不要让它继续或执行任何其他工作。这阻止了UI线程,并且只是在UI线程中正常工作(开销实际上使其变得更糟)。

您需要做的是为任务添加一个延续,该任务可以处理任务完成时您想要执行的任何工作,然后让方法结束,以便UI线程可以继续做其他工作。

private Task<string> ImportXML()
{
    ParseXML.ParseXML parsexml = new ParseXML.ParseXML(chosenFile);

    ProgressDialog progress = new ProgressDialog(this);
    progress.SetMessage(GetString(Resource.String.ImportXML));
    progress.Indeterminate = true;
    progress.Show();
    var task = Task.Factory.StartNew(() => parsexml.ProcessXML());
    task.ContinueWith(t => progress.Hide());
    return task;
}

因为此方法必须是异步的,允许UI线程继续执行其工作,所以它需要返回Task,而不是实际的字符串。然后,此方法的调用者可以向此方法调用的结果添加延续以使用结果。