Android AsyncTask - 避免多个实例同时运行

16

我有一个AsyncTask用于处理后台HTTP任务,它是按计划(Alarms/Service)运行的,有时用户也会手动执行它。

我从SQLite中处理记录,并注意到在服务器上出现了重复发布的情况,这告诉我有时预定任务和同时用户手动运行它导致相同的记录被读取并从数据库中处理两次。我在处理完成后删除记录,但仍然会出现这种情况。

我应该如何处理?也许需要组织某种类型的队列?

7个回答

17

您可以使用executeOnExecutor()Executor上执行您的AsyncTask

为了确保线程以串行方式运行,请使用:SERIAL_EXECUTOR

其它: 如何使用Executor

如果多个活动访问您的DB,为什么不创建一种网关数据库助手,并使用synchronized块确保只有一个线程可以同时访问它。


11

或者,您可以尝试使用以下方法来查看任务当前是否正在运行:

if (katitsAsyncTask.getStatus().equals(AsyncTask.Status.FINISHED))
     katitsAsyncTask.execute();
else
     // wait until it's done.

实际上,我也在我的服务中运行它。我的服务保持引用并且在现有的引用完成之前不会启动新的引用。我只需保留一个希望使用它的活动队列,并按顺序逐个运行它们,但一次只进行单个调用...它确实起作用。 - BonanzaDriver
我的服务并不总是运行,我使用闹钟来执行它。我可以在数据库中使用“硬”标志,但我担心如果AsyncTask因任何原因死亡并且无法更新此标志,则另一个任务将永远不会运行。我可以使用时间戳,并在AsyncTask运行一定时间后声明其“死亡”,但我不喜欢这种解决方案。 - katit
由于这些是后台网络服务,我建议使用“非粘性”服务。如果您正在使用适当的错误处理并担心某个服务可能会“死在藤上”,那么请使用跟踪记录被提交的记录的技术,或者允许第二个实例,如果自第一个实例启动以来已经过去了XXX时间,或者......只有您的想象力在此限制您。 - BonanzaDriver
什么是非粘性?我使用AlarmManager在预定义的时间间隔内启动我的服务。我认为这种方法对电池寿命最好,而且更容易处理。我确实处理错误,但你永远不知道用户是否关闭手机或发生了其他情况,而你已经在某个地方记录了“Running”=true。 - katit
@katit 你最终是如何实现它的? - powder366
显示剩余2条评论

6

将AsyncTask初始化为null。只有在其为null时才创建一个新的AsyncTask。在onPostExecute中,最后将其再次设置为null。如果用户取消了该操作,在onCancelled中也要做同样的处理。下面是一些未经测试的代码,以说明基本思想。

import android.app.Activity;
import android.os.AsyncTask;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;

public class FooActivity extends Activity {

    private class MyAsyncTask extends AsyncTask<Foo, Foo, Foo> {
        @Override
        protected void onPostExecute(Foo foo) {
                    // do stuff
            mMyAsyncTask = null;
        }

        @Override
        protected void onCancelled() {
            // TODO Auto-generated method stub
            mMyAsyncTask = null;
        }

        @Override
        protected Foo doInBackground(Foo... params) {
                    try {
                         // dangerous stuff                         
                    } catch (Exception e) {
                        // handle. Now we know we'll hit onPostExecute()
                    }

            return null;
        }
    }

    private MyAsyncTask mMyAsyncTask = null;

    @Override
    public void onCreate(Bundle bundle) {
        Button button = (Button) findViewById(R.id.b2);
        button.setOnClickListener(new View.OnClickListener() {

            public void onClick(View v) {
                if (mMyAsyncTask == null) {
                    mMyAsyncTask = new MyAsyncTask();
                    mMyAsyncTask.execute(null);
                }
            }

        });
    }

}


我尝试了其他所有解决方案,但在尝试在AsyncTask内访问数据库时仍然遇到了“db not open”异常,这是对我有效的方法。谢谢。 - Sayed Jalil Hassan

1

我知道这已经过去一段时间了,而且你已经解决了你的问题。但是我刚刚遇到了类似的问题。Reno的建议让我找到了正确的方向,但是对于那些发现填补空白很困难的人来说,这就是我如何克服与katit类似问题的方法。

我希望特定的AsyncTask只在没有运行时才运行。正如Reno的建议所述,AsyncTask接口已被创建以处理所有繁琐的进程,以便正确地处理Android线程。这意味着,执行程序已内置。正如blog所建议的:

“当在AsyncTask上调用execute(Object.. params)时,任务将在后台线程中执行。根据平台的不同,AsyncTasks可以串行执行(1.6之前和4+可能再次执行),也可以并行执行(1.6-3.2)。

为确保按您要求串行或并行运行,从API Level 11开始,您可以使用executeOnExecutor(Executor executor, Object.. params)方法,并提供一个执行程序。平台为方便起见提供了两个执行程序,分别为AsyncTask.SERIAL_EXECUTOR和AsyncTask.THREAD_POOL_EXECUTOR。”

因此,你可以通过AsyncTask接口来进行线程阻塞,这也意味着你可以像DeeV在post中建议的那样,简单地使用AsyncTasks.getStatus() 来处理线程阻塞。

在我的代码中,我通过以下方式处理:

创建一个全局变量:

private static AsyncTask<String, Integer, String> mTask = null;

在onCreate中,将其初始化为我的AsyncTask的实例,称为CalculateSpecAndDraw:

mTask = new CalculateAndDrawSpec();

现在,每当我想调用这个AsyncTask时,我都会将execute包围在以下代码中:
if(mTask.getStatus() == AsyncTask.Status.FINISHED){
             // My AsyncTask is done and onPostExecute was called
            mTask = new CalculateAndDrawSpec().execute(Integer.toString(progress));
        }else if(mTask.getStatus() == AsyncTask.Status.PENDING){
            mTask.execute(Integer.toString(progress));
        }else{
            Toast.makeText(PlaySpecActivity.this, "Please Wait..", 1000).show();

        }

如果一个线程已经完成,此操作将会生成一个新的线程;或者如果线程状态为PENDING即已定义但未启动,则我们启动它。但如果线程正在运行,我们不会重新运行它,只是简单地通知用户它尚未完成,或执行任何我们希望进行的操作。如果您想安排下一个事件而不仅仅是阻止其重新运行,请参阅this关于使用执行器的文档。


1

你可以尝试将你的 check-what-to-sendsend-it 逻辑放在一个 synchronized 方法中,这种方法对我们来说似乎很有效。


0

尝试创建一些实例布尔值,在异步任务的preexecute中将其设置为“true”,然后在postexecute中将其设置为“false”。然后,可能在doinbackground中检查该布尔值是否为true。如果是,则调用特定重复任务的cancel方法。


1
我担心如果我在应用程序中使用标志(flag),并且我的AsyncTask由于某些原因失败,那么它将永远不会再次执行,因为标志已经显示它已经开始了。 - katit

0
你可以在共享首选项中保存任务的状态。在开始任务之前检查该值(可能是布尔值)。在onPostExecute中将状态设置为已完成(true?),在onPreExecute或构造函数中将其设置为false。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接