Android 中的两个并发 AsyncTasks - 如何处理竞争条件?

Posted

技术标签:

【中文标题】Android 中的两个并发 AsyncTasks - 如何处理竞争条件?【英文标题】:Two concurrent AsyncTasks in Android - How to handle race conditions? 【发布时间】:2020-05-29 16:52:49 【问题描述】:

每次按下按钮和释放按钮时,我都会发送一个 HTTP Post。在释放按钮之前,我可能不会收到来自“按下”POST 的响应,因此我尝试同时运行这些任务。但是,有时,释放按钮的 POST 请求会在按下按钮的请求之前发送到服务器。我怎样才能避免/解决这个问题?

button = (ImageButton) findViewById((R.id.button1));
        button.setOnTouchListener(new OnTouchListener() 
            @Override
            public boolean onTouch(View v, MotionEvent event) 
                HTTPAsyncTask task = new HTTPAsyncTask();
                if (event.getAction() == MotionEvent.ACTION_DOWN ) 
                    task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, Utilities.StringCollection.rotateRight);
                    return true;
                
                else if (event.getAction() == MotionEvent.ACTION_UP)
                    task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, Utilities.StringCollection.stop);
                    return true;
                
                return false;
            
        );

【问题讨论】:

【参考方案1】:

从概念上讲,您的 HTTPAsyncTask doInBackground() 包含 2 个不同的操作:

    连接并将请求排队到套接字 接收响应

(之后,当然会调用onPostExecute()。)

正如您所描述的,您需要在两个不同的线程上控制动作 #1 的执行顺序,同时允许动作 #2 并行发生。

使用通用AsyncTask 机制是不可能的,因为您没有提供任何“将任务一分为二”的方法;线程 A 无法确定线程 B 何时从动作 #1 切换到动作 #2,反之亦然。

可能会添加这样的逻辑,但你没有解释你是如何进行套接字调用的。

不过,提供这些额外的细节并没有什么意义;与网络请求相关的边缘情况会破坏您可能拥有的任何订单保证。网络有损;请求可能失败,响应可能失败。请求可能到达乱序,即使它们是按顺序发送的(特别是如果您使用两个单独的套接字)。自动重试可能需要几秒钟才能完成。

IOW,“已按下”的请求可能会被网络丢失,您可能必须在“未按下”的请求完成后的某个时间将其重新排入套接字。

我建议您放弃您正在寻求的“部分条件并发”,并在执行第二个之前确保第一个 HTTPAsyncTask 已完成。

或者,您可以为每个 POST 添加一个连续的“请求 ID”,或者在第二个请求中包含有关第一个请求的信息,以便服务器能够整理出发生了什么,即使请求无序到达。

【讨论】:

【参考方案2】:

如果您的最小 SDK 为 11 "HONEYCOMB",您可以像这样调用 asyncTask 以在单线程中运行它以防止它们之间出现竞争条件:

button = (ImageButton) findViewById((R.id.button1));
        button.setOnTouchListener(new OnTouchListener() 
            @Override
            public boolean onTouch(View v, MotionEvent event) 
                HTTPAsyncTask task = new HTTPAsyncTask();
                if (event.getAction() == MotionEvent.ACTION_DOWN ) 
                    task.execute(Utilities.StringCollection.rotateRight);// using one thread
                    return true;
                
                else if (event.getAction() == MotionEvent.ACTION_UP)
                    task.execute(Utilities.StringCollection.stop);// using one thread
                    return true;
                
                return false;
            
        );

重要提示:网络 I/O 不可靠,网络包可能会丢失或无法按发送顺序接收,但我在标题中看到了问题

android 中的两个并发 AsyncTasks - 如何处理竞争条件? "

然后就回答了。因此,您无法确定是否以这种方式收到了一个又一个请求。我建议您在一个请求中向服务器发送两个事件并在服务器中对其进行分析。

【讨论】:

两个不同的触摸事件,意味着两个不同的HTTPAsyncTasks,意味着每个都可以在自己的线程上执行。还是我错过了什么? @greeble31 我只用execute 更改executeOnExecutor,所有其他代码均未更改。他的问题是@AndroidNewbie1 代码 @greeble31 你错过了两个AsyncTask 并不总是意味着两个不同的线程。因为api 11,如果你不在线程池上运行它们,你在一个工作线程中运行它们。如果您在一个工作线程中运行它们,它们将一个接一个地运行,即使您使用的AsyncTask 实例并不重要,所有AsyncTask 将在一个工作线程中运行。但如果你使用executeOnExecutor,它们将并行在不同的线程上。 好吧,我不知道,但不幸的是,在编辑问题之前我无法删除我的反对票。但是我想指出这仍然没有解决最初的问题,即 OP 希望允许某些操作并行执行。这个答案确保它们在 HONEYCOMB 之后连续执行。请参阅我的答案以供参考。 @greeble31 我知道网络 I/O 不可靠,网络包可能会丢失或无法按发送顺序接收,但我在标题“Android 中的两个并发 AsyncTasks -如何处理比赛条件?” 刚刚回答了这个问题。我会更新我的答案来指出这一点。

以上是关于Android 中的两个并发 AsyncTasks - 如何处理竞争条件?的主要内容,如果未能解决你的问题,请参考以下文章

如何将变量传入和传出AsyncTasks?

Android:同时进行两个活动

为什么我的线程组列表中有这么多的绑定器和asynctasks?他们是如何管理的?

Android AsyncTask 上下文行为

Jellybean 中的 Android SQLite 数据库事务/锁定更改

您将如何在 Utils/Helper 类中使用 AsyncTasks