由回调写入并在活动中读取的布尔值的竞争条件

时间:2016-04-24 01:42:30

标签: android race-condition

在Android活动中,我发送由用户按下UI上的按钮驱动的http请求。 我不希望同时运行多个请求(OutlookClient崩溃)。

我的问题是:是否有可能因为回调而导致竞争条件,结果是在发送新请求之前写入相同的布尔值(使用runOnUiTread)?

由于

// Should this be either "volatile" or atomic ??
private boolean isThereAPendingRequest = false;

@Override
protected void onCreate(Bundle savedInstanceState) {

    genericClient = clientInitializer.create(this);
    // ...

    isThereAPendingRequest = true; // still have to login

    Futures.addCallback(genericClient.logon(this, scopes), new FutureCallback<Boolean>() {
        @Override
        public void onSuccess(Boolean result) {

            // ...
            isThereAPendingRequest = false;
        }

        @Override
        public void onFailure(@NonNull Throwable t) {

            // ...
            isThereAPendingRequest = false;
        }
    });

    // ...
}

// ...

public void getBookings(View view){

    if(isThereAPendingRequest){
        Toast.makeText(getApplicationContext(), "There's already a pending request. Try in a few seconds.", Toast.LENGTH_LONG).show();
        return;
    }

    isThereAPendingRequest = true;

    Futures.addCallback( genericClient.getCalendarEvents(), new FutureCallback<List<List>>(){
        @Override
        public void onSuccess(final List<List> resultCalendars) {

            Log.d("APP", "Success. Result: "+resultCalendars);

            runOnUiThread(new Runnable() {
                @Override
                public void run() {

                    // ..

                    isThereAPendingRequest = false;
            }
        }
    }
    // ..
}

public void sendBooking(View view){

    if(isThereAPendingRequest){
        Toast.makeText(getApplicationContext(), "There's already a pending request. Try in a few seconds.", Toast.LENGTH_LONG).show();
        return;
    }

    isThereAPendingRequest = true;

    Futures.addCallback( genericClient.sendBooking( booker, title), new FutureCallback<List<String>>(){
        @Override
        public void onSuccess(final List<String> resultBooking) {

              Log.d("APP", "Success. Result: "+resultBooking);

                runOnUiThread(new Runnable() {
                    @Override
                    public void run() {

                      // ...
                      isThereAPendingRequest = false;
                    }
                });
        }
        @Override
        public void onFailure(Throwable t) {

           Log.e( "APP", "Delete error. Cause: "+t.getLocalizedMessage() );

           // ...

           Toast.makeText(getApplicationContext(), "Fail!", Toast.LENGTH_LONG).show();

           isThereAPendingRequest = false;
        }
   });

   }catch(Exception ex){
       // logger
       isThereAPendingRequest = false;
   }
}

更新:这是Futures中调用的函数之一..

public ListenableFuture<List<List>> getCalendarEvents(){

// logger

final SettableFuture<List<List>> future = SettableFuture.create();

DateTime now = new DateTime(DateTimeZone.UTC);
DateTime workDayEnd = new DateTime( now.getYear(), now.getMonthOfYear(), now.getDayOfMonth(), 23, 59, 0 );

Futures.addCallback(

        mClient .getMe()
                .getCalendarView()
                .addParameter("startDateTime", now)
                .addParameter("endDateTime", workDayEnd)
                .read(),
        new FutureCallback<OrcList<Event>>() {
            @Override
            public void onSuccess(final OrcList<Event> result) {

                // ...

                future.set(myList);
            }

            @Override
            public void onFailure(@NonNull Throwable t) {
                // ...
                future.setException(t);
            }
        }
);

return future;

}

1 个答案:

答案 0 :(得分:1)

如果始终在UI线程上调用getBookingssetBookings,那么你应该没问题。您知道,当isThereAPendingRequest设置为false时,请求必须已经完成,因此您可以放心使用。顺便说一下,Futures.addCallback有一个替代签名,允许您明确传入Executor,因此如果您使用它,则不需要调用runOnUiThread来减少某些代码嵌套。

但是,如果您打算同时调用这些方法,我会看到至少一个需要锁定以阻止的竞争条件。如果您有兴趣,请详细了解。

编辑完整性:

问题表明您的目标是防止两个请求同时运行。有两种情况可能发生:

  1. isThereAPendingRequest==false,但实际上有待处理的请求。到目前为止,您的代码是安全的,因为您只在请求完成后将其设置为false。你甚至不需要volatile
  2. 在不同的线程上调用
  3. getBookings和/或setBookings。如果他们同时达到if(isThereAPendingRequest)会怎样?他们可以同时(并且正确地)看到它是假的,将其设置为true,然后两者都独立发送请求并导致您崩溃。
  4. 您不必担心(1),并且(2)不应该是一个问题,只要您始终在同一个线程上调用这些方法即可。