RecyclerView and DiffUtil - A Concurrency Nightmare

The documentation for DiffUtil suggests creating a DiffUtil.DiffResult in the background thread due to potential lengthy calculations. This seems like a bad idea to me, because this thread may work with stale data in a situation like the following (assuming list access is thread safe):

  • Add data to list and notify adapter
  • You need to replace list with newList , which will have different additions and some deletions
  • Call DiffUtil.calculateDiff in the background and get DiffResult for list and newList , and post the message in the main thread that newList will use, and call DiffResult.dispatchUpdatesTo
  • Before processing this message, the user takes action on the main thread, which causes mutations in list
  • The message is processed, newList set as a new data source, and DiffResult.dispatchUpdatesTo triggered, causing an inconsistent representation of the underlying data + loss of any mutations, as DiffResults calculated

It’s good that there’s nothing good, so let the changes begin with step 3:

  1. Set newList as the new data source, call DiffUtil.calculateDiff in the background and get the DiffResult for list and newList , and post the message in the main thread that will call DiffResult.dispatchUpdatesTo
  2. Before processing this message, the user takes action in the main thread, which causes mutations in newList , and notifies the adapter, causing data representation inconsistency, since DiffResult.dispatchUpdatesTo has not yet been called

There are a few more variations in this, but none of them are good. It seems that the only way to reliably use DiffUtil with a large DiffUtil to set the DiffUtil to either disable or queue all updates until DiffResult.dispatchUpdatesTo is called.

Did I miss something that would make it false?

+5
source share
2 answers

Take a look at the BatchingListUpdateCallback. This is the class that completes the main callback, and when a multiple change occurs in the list, batchingListCallback will only notify after the main callback.

https://developer.android.com/reference/android/support/v7/util/BatchingListUpdateCallback.html

EDIT

I'm sorry that my answer is incorrect.

I looked in the source code for DiffUtil. and I see that BatchingListUpdateCallback is used in the dispatchUpdatesTo method anyway.

  public void dispatchUpdatesTo(ListUpdateCallback updateCallback) { final BatchingListUpdateCallback batchingCallback; if (updateCallback instanceof BatchingListUpdateCallback) { batchingCallback = (BatchingListUpdateCallback) updateCallback; } else { batchingCallback = new BatchingListUpdateCallback(updateCallback); // replace updateCallback with a batching callback and override references to // updateCallback so that we don't call it directly by mistake //noinspection UnusedAssignment updateCallback = batchingCallback; } 

But as a vector for the right path, this may be useful :)

+2
source

I finished building updates to control user interactions during DiffUtil calculations and still only accessed the dataset in the main thread.

I wrote there: https://geoffreymetais.imtqy.com/code/diffutil-threading/

+2
source

Source: https://habr.com/ru/post/1258907/


All Articles