C# Android任务线程和UI
我正在尝试在后台/异步/运行大型XML文件的导入-换句话说,我正试图让它脱离UI线程 我的代码如下:C# Android任务线程和UI,c#,android,multithreading,asynchronous,C#,Android,Multithreading,Asynchronous,我正在尝试在后台/异步/运行大型XML文件的导入-换句话说,我正试图让它脱离UI线程 我的代码如下: private string ImportXML() { string returnFile = null; ParseXML.ParseXML parsexml = new ParseXML.ParseXML (chosenFile); ProgressDialog progress = new ProgressDialog(this)
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线程中运行,因为我暂时尝试在while循环中进行的UI更新没有发生-我根本看不到进度
该操作可能需要几秒钟的时间——对于我的一些示例文件,最多需要10秒钟——因此我不喜欢使用AsyncTask,因为我认为不建议将其用于长时间的操作
有人能告诉我这个问题出在哪里吗?我创建了一个示例AsyncTask,向您展示如何独立于ui线程处理数据。此AsyncTask将从资源中加载字符串并将其设置为TextView
重要提示:通常情况下,从资源中加载字符串永远不需要AsyncTask。我只是以这个为例来演示如何在单独的线程中执行工作
对于任何异步任务,只有一件事需要记住:您在单独的线程中执行工作,而不是在ui线程中。当独立线程完成时,无法保证您引用的ui元素仍然存在,甚至无法保证您的应用程序仍在运行。因此,如果您引用一个活动或上下文或某个ui元素,如TextView或Button或任何其他复杂对象,而这些对象在后台线程结束时可能不再存在,则必须使用WeakReference。WeakReferences是一种巧妙的方法,它允许对您正在引用的对象进行垃圾收集,从而绕过这个问题。在AsyncTask中,您可以检查所需的对象是否仍然存在,如果仍然存在,则可以在不造成内存泄漏的情况下执行工作。您可以使用这样的WeakReference:
WeakReference<TextView> textViewReference = new WeakReference<TextView>(textView);
TextView textView = textViewReference.get();
TextView textView = textViewReference.get();
if(textView != null) {
// TextView still exists.
} else {
// TextView doesn't exist anymore. Most likely he has already been garbage collected.
}
如果您引用的TextView不再存在,get将简单地返回null。因此,要检查TextView是否仍然存在,您可以这样做:
WeakReference<TextView> textViewReference = new WeakReference<TextView>(textView);
TextView textView = textViewReference.get();
TextView textView = textViewReference.get();
if(textView != null) {
// TextView still exists.
} else {
// TextView doesn't exist anymore. Most likely he has already been garbage collected.
}
如果您理解关于如何以及何时使用weakreference的这一简单原则,那么实现异步任务应该没有问题。我已经用注释解释了有关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);
}
}
}
}
是的,您可以创建一个任务来在非UI线程中执行某些工作。然后告诉UI线程坐在那里等待其他任务完成。在任务完成之前,不要让它继续或做任何其他工作。这会阻塞UI线程,这并不比只在UI线程中正确地执行工作好,开销实际上会使情况变得更糟 您需要做的是向任务添加一个continuation,它可以在任务完成时处理您想要做的任何工作,然后让该方法结束,以便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线程继续工作,所以它需要返回一个任务,而不是实际的字符串。然后,此方法的调用方可以向此方法调用的结果添加一个延续以使用结果。使用异步任务,它非常适合于此。您确定您的代码正在单独的线程中运行吗?我的意思是ImportXML方法是否在单独的线程中运行?当然,您不会看到使用非UI线程更新进度的更新。。。您必须调用UI线程才能执行此操作。@XaverKapeller感谢您的回复。我对Android和c开发有点陌生,所以我有这样的印象,通过以这种方式调用ImportXML,我会强制它位于一个单独的线程中。从你的回答来看,我想情况不是这样的?不,不是。我将用一个示例AsyncTask发布一个答案。@XaverKapeller所以它使用的是C1.0?为什么lambda不能工作?谢谢@Servy-我已经对ImportXML方法做了这些修改,使之成为一项任务。在调用此任务的方法中,我有以下内容:如果chosenFile.EndsWith.xml,StringComparison.OrdinalIgnoreCase dbFile=ImportXML.Result;但是通过追踪,我似乎再次通过这样做阻塞了线程。@Yromulus,这是正确的;结果块,直到有结果为止。您应该将需要使用此任务结果本身的工作作为此方法任务的延续。这就是异步程序的含义。谢谢@Servy-通过您的建议和更多的研究,我现在有了一个可行的解决方案-我非常感谢您的帮助。问题的标签是C,而不是Java。这是Java代码。我对这个问题看得不够清楚。他从来没有提到他正在使用Xamarin或任何类似的药物。但是,一旦您在获取后持有引用,它就不能再被垃圾收集,因为您再次直接持有引用。谢谢@XaverKapeller-很抱歉,我没有更详细地解释我使用的是Xamarin和C。我将尝试Servy之前给出的C示例 我尝试一下您给我的异步任务示例。