क्या मैं वॉली के साथ एक तुल्यकालिक अनुरोध कर सकता हूं?


133

कल्पना कीजिए कि मैं एक ऐसी सेवा में हूं जिसमें पहले से ही एक पृष्ठभूमि धागा है। क्या मैं उसी धागे में वॉली का उपयोग करके एक अनुरोध कर सकता हूं, ताकि कॉलबैक समकालिक रूप से हो?

इसके 2 कारण हैं: - पहला, मुझे दूसरे धागे की आवश्यकता नहीं है और इसे बनाना एक बेकार होगा। - दूसरा, अगर मैं एक ServiceIntent में हूं, तो कॉलबैक से पहले थ्रेड का निष्पादन समाप्त हो जाएगा, और उसके बाद मेरे पास वॉली से कोई प्रतिक्रिया नहीं होगी। मुझे पता है कि मैं अपनी खुद की सेवा बना सकता हूं जिसमें एक रनलूप के साथ कुछ धागे हैं जिन्हें मैं नियंत्रित कर सकता हूं, लेकिन वॉली में यह कार्यक्षमता होना वांछनीय होगा।

धन्यवाद!


5
@ ब्लंडेल की प्रतिक्रिया के साथ-साथ अत्यधिक उत्कीर्ण (और बहुत उपयोगी) उत्तर को पढ़ना सुनिश्चित करें।
जेडीडजा

जवाबों:


183

ऐसा लगता है कि यह वॉली की RequestFutureकक्षा के साथ संभव है । उदाहरण के लिए, एक तुल्यकालिक JSON HTTP GET अनुरोध बनाने के लिए, आप निम्न कार्य कर सकते हैं:

RequestFuture<JSONObject> future = RequestFuture.newFuture();
JsonObjectRequest request = new JsonObjectRequest(URL, new JSONObject(), future, future);
requestQueue.add(request);

try {
  JSONObject response = future.get(); // this will block
} catch (InterruptedException e) {
  // exception handling
} catch (ExecutionException e) {
  // exception handling
}

5
@tasomaniac अपडेट किया गया। यह JsonObjectRequest(String url, JSONObject jsonRequest, Listener<JSONObject> listener, ErrorListener errorlistener)कंस्ट्रक्टर का उपयोग करता है । RequestFuture<JSONObject>दोनों Listener<JSONObject>और ErrorListenerइंटरफेस को लागू करता है, इसलिए इसे अंतिम दो मापदंडों के रूप में उपयोग किया जा सकता है।
मैट

21
यह हमेशा के लिए अवरुद्ध है!
मोहम्मद सुबी शेख कुरुश

9
संकेत: यदि आप भविष्य को कॉल करते हैं तो यह हमेशा के लिए अवरुद्ध हो सकता है। () इससे पहले कि आप अनुरोध कतार में अनुरोध जोड़ दें।
२०

3
यह हमेशा के लिए अवरुद्ध हो जाएगा क्योंकि आपके पास एक कनेक्शन त्रुटि हो सकती है पढ़ने के लिए ब्लंडेल उत्तर
मीना गैब्रियल

4
एक कहना चाहिए कि आपको मुख्य धागे पर ऐसा नहीं करना चाहिए। यह मेरे लिए स्पष्ट नहीं था। यदि मुख्य थ्रेड future.get()तब तक अवरुद्ध हो जाता है, तो एप्लिकेशन स्टाल या रनआउट में निश्चित रूप से सेट होने पर रन करेगा।
rttandy

125

नोट @ मैथ्यूज़ का उत्तर सही है, लेकिन यदि आप किसी अन्य थ्रेड पर हैं और आपके पास कोई इंटरनेट नहीं होने पर वॉली कॉल करते हैं, तो आपकी त्रुटि कॉलबैक को मुख्य थ्रेड पर कॉल किया जाएगा, लेकिन आप जिस थ्रेड पर हैं, वह हमेशा के लिए अवरोधित हो जाएगा। (इसलिए यदि वह धागा एक IntentService है, तो आप कभी भी इसे एक और संदेश नहीं भेज पाएंगे और आपकी सेवा मूल रूप से मृत हो जाएगी)।

उस समय के संस्करण का उपयोग करें जिसमें get()टाइमआउट है future.get(30, TimeUnit.SECONDS)और अपने धागे से बाहर निकलने के लिए त्रुटि को पकड़ें।

@ मैथ्यूज मैच का जवाब देने के लिए:

        try {
            return future.get(30, TimeUnit.SECONDS);
        } catch (InterruptedException e) {
            // exception handling
        } catch (ExecutionException e) {
            // exception handling
        } catch (TimeoutException e) {
            // exception handling
        }

नीचे मैंने इसे एक विधि में लपेटा और एक अलग अनुरोध का उपयोग किया:

   /**
     * Runs a blocking Volley request
     *
     * @param method        get/put/post etc
     * @param url           endpoint
     * @param errorListener handles errors
     * @return the input stream result or exception: NOTE returns null once the onErrorResponse listener has been called
     */
    public InputStream runInputStreamRequest(int method, String url, Response.ErrorListener errorListener) {
        RequestFuture<InputStream> future = RequestFuture.newFuture();
        InputStreamRequest request = new InputStreamRequest(method, url, future, errorListener);
        getQueue().add(request);
        try {
            return future.get(REQUEST_TIMEOUT, TimeUnit.SECONDS);
        } catch (InterruptedException e) {
            Log.e("Retrieve cards api call interrupted.", e);
            errorListener.onErrorResponse(new VolleyError(e));
        } catch (ExecutionException e) {
            Log.e("Retrieve cards api call failed.", e);
            errorListener.onErrorResponse(new VolleyError(e));
        } catch (TimeoutException e) {
            Log.e("Retrieve cards api call timed out.", e);
            errorListener.onErrorResponse(new VolleyError(e));
        }
        return null;
    }

यह एक महत्वपूर्ण बिंदु है! सुनिश्चित नहीं हैं कि इस उत्तर ने अधिक उत्थान नहीं किया है।
जेडीडजा

1
यह भी ध्यान देने योग्य है कि यदि आप एक्ज़ीक्यूशन एक्सेप्शन को उसी श्रोता पर पास करते हैं जो आपने अनुरोध में पास किया था तो आप अपवाद को दो बार संसाधित करेंगे। यह अपवाद तब होता है जब अनुरोध के दौरान कोई अपवाद हुआ है जो वॉली आपके लिए त्रुटि सूची से होकर गुजरेगा।
स्टिमसोनी

@Blundell मैं आपका जवाब नहीं समझता। यदि श्रोता को UI थ्रेड पर निष्पादित किया जाता है, तो आपके पास प्रतीक्षा में पृष्ठभूमि थ्रेड है और UI थ्रेड जो InformAll () को कॉल करता है, इसलिए यह ठीक है। एक डेडलॉक हो सकता है यदि वितरण उसी धागे पर किया जाता है जिसे आप भविष्य के प्राप्त () के साथ अवरुद्ध कर रहे हैं। तो आपकी प्रतिक्रिया बिना किसी मतलब के लगती है।
ग्रेवुल्फ़ 82

1
@ greywolf82 IntentServiceएक एकल धागे का एक थ्रेड पूल निष्पादक है, इसलिए कि इंटेंससेवा हमेशा के लिए अवरुद्ध हो जाएगा, यह एक पाश में बैठता है
ब्लंडेल

@ बेलंडेल मुझे समझ में नहीं आता है। यह तब तक बैठता है जब तक कि एक सूचना नहीं दी जाएगी और इसे यूआई थ्रेड से बुलाया जाएगा। जब तक आपके पास दो अलग-अलग धागे हैं, मैं गतिरोध नहीं देख सकता हूं
ग्रेवुल्फ 82

9

संभवतः फ्यूचर्स का उपयोग करने की अनुशंसा की जाती है, लेकिन यदि आप जो भी कारण नहीं चाहते हैं, तो अपनी खुद की सिंक्रनाइज़ अवरुद्ध चीज़ को पकाने के बजाय आपको एक का उपयोग करना चाहिए java.util.concurrent.CountDownLatch। ताकि इस तरह से काम हो सके ।।

//I'm running this in an instrumentation test, in real life you'd ofc obtain the context differently...
final Context context = InstrumentationRegistry.getTargetContext();
final RequestQueue queue = Volley.newRequestQueue(context);
final CountDownLatch countDownLatch = new CountDownLatch(1);
final Object[] responseHolder = new Object[1];

final StringRequest stringRequest = new StringRequest(Request.Method.GET, "http://google.com", new Response.Listener<String>() {
    @Override
    public void onResponse(String response) {
        responseHolder[0] = response;
        countDownLatch.countDown();
    }
}, new Response.ErrorListener() {
    @Override
    public void onErrorResponse(VolleyError error) {
        responseHolder[0] = error;
        countDownLatch.countDown();
    }
});
queue.add(stringRequest);
try {
    countDownLatch.await();
} catch (InterruptedException e) {
    throw new RuntimeException(e);
}
if (responseHolder[0] instanceof VolleyError) {
    final VolleyError volleyError = (VolleyError) responseHolder[0];
    //TODO: Handle error...
} else {
    final String response = (String) responseHolder[0];
    //TODO: Handle response...
}

जब से लोगों को वास्तव में ऐसा करने की कोशिश कर रहा था और मुझे लगता है कि मैं वास्तव में उपयोग में इस का एक "वास्तविक जीवन" काम कर नमूना प्रदान करेगा फैसला किया कुछ परेशानी में भाग गया लग रहा था। यहाँ यह https://github.com/timolehto/SynchronousVolleySample है

अब भले ही समाधान काम करता है, इसकी कुछ सीमाएँ हैं। सबसे महत्वपूर्ण बात, आप इसे मुख्य UI थ्रेड पर नहीं कह सकते। वॉली पृष्ठभूमि पर अनुरोधों को निष्पादित करता है, लेकिन डिफ़ॉल्ट रूप से वॉली Looperप्रतिक्रियाओं का प्रेषण करने के लिए आवेदन के मुख्य का उपयोग करता है । यह एक गतिरोध का कारण बनता है क्योंकि मुख्य यूआई थ्रेड प्रतिक्रिया की प्रतीक्षा कर रहा है, लेकिन डिलीवरी को संसाधित करने से पहले समाप्त होने की Looperप्रतीक्षा कर रहा onCreateहै। क्या तुम सच में वास्तव में के बजाय स्थिर सहायक तरीकों में से यह आप कर सकते थे करने के लिए, चाहते हैं, अपने स्वयं के दृष्टांत RequestQueueगुजर इसे अपने ExecutorDeliveryसे बंधा एक Handlerका उपयोग कर एक Looperहै जो मुख्य यूआई धागे से अलग धागा से जुड़ा हुआ है।


यह समाधान मेरे धागे को हमेशा के लिए अवरुद्ध कर देता है, काउंटडाउन लैंथ के बदले थ्रेड.स्लीप को बदल दिया और समस्या हल हो गई
sersesyan

यदि आप इस तरीके से असफल होने वाले कोड का पूरा नमूना प्रदान कर सकते हैं, तो हम यह पता लगा सकते हैं कि समस्या क्या है। मैं नहीं देखता कि कैसे उलटी गिनती के साथ संयोजन के साथ सो जाना समझ में आता है।
तिमु

ठीक है @VinojVetha मैंने स्थिति को स्पष्ट करने के लिए उत्तर को थोड़ा अपडेट किया है और एक GitHub रेपो प्रदान किया है जिसे आप आसानी से क्लोन कर सकते हैं और कोड को कार्रवाई में आज़मा सकते हैं। यदि आपके पास अधिक समस्याएं हैं, तो कृपया संदर्भ के रूप में अपनी समस्या का प्रदर्शन करने वाले नमूना रेपो का एक कांटा प्रदान करें।
टिमो

यह अब तक के तुल्यकालिक अनुरोध के लिए एक महान समाधान है।
बिक्रम

2

@Blundells और @Mathews दोनों जवाबों के पूरक अवलोकन के रूप में, मुझे यकीन नहीं है कि वॉली द्वारा किसी भी कॉल को कुछ भी दिया जाता है लेकिन मुख्य धागा।

स्रोत

पर एक नज़र होने RequestQueueकार्यान्वयन ऐसा लगता है RequestQueueएक उपयोग कर रहा है NetworkDispatcherअनुरोध को निष्पादित करने के लिए और एक ResponseDeliveryपरिणाम देने के लिए ( ResponseDeliveryमें इंजेक्ट किया जाता NetworkDispatcher)। ResponseDeliveryएक साथ बनाया बारी में है Handlerमुख्य थ्रेड से अंडे (कहीं आस-पास में 112 लाइन RequestQueueकार्यान्वयन)।

NetworkDispatcherकार्यान्वयन में लाइन 135 के बारे में कहीं ऐसा लगता है कि सफल परिणाम ResponseDeliveryभी किसी भी त्रुटि के माध्यम से दिए जाते हैं। फिर; एक ResponseDeliveryएक के आधार पर Handlerमुख्य थ्रेड से अंडे।

दलील

उपयोग-मामले के लिए जहां यह अनुरोध IntentServiceउचित है कि यह मानने के लिए कि सेवा का धागा तब तक अवरुद्ध होना चाहिए जब तक कि हमारे पास वॉली की प्रतिक्रिया न हो (परिणाम को संभालने के लिए एक जीवित रनटाइम गुंजाइश की गारंटी देने के लिए)।

सुझाए गए उपाय

एक दृष्टिकोण डिफ़ॉल्ट तरीका एक ओवरराइड करने के लिए किया जाएगा RequestQueueबनाई गई है , जहां एक वैकल्पिक निर्माता के बजाय प्रयोग किया जाता है, इंजेक्शन लगाने के एक ResponseDeliveryजहाँ से spawns वर्तमान धागा बजाय मुख्य थ्रेड। मैंने हालांकि इसके निहितार्थों की जांच नहीं की है।


1
एक कस्टम रिस्पॉन्सडाईवरी कार्यान्वयन को लागू करना इस तथ्य से जटिल है कि क्लास और क्लास finish()में विधि पैकेज निजी है, इसके अलावा एक प्रतिबिंब हैक का उपयोग करने के अलावा मुझे यकीन नहीं है कि इसके आसपास एक तरीका है। मैंने मुख्य (UI) थ्रेड पर चलने वाली किसी भी चीज़ को रोकने के लिए क्या किया, वैकल्पिक लूपर थ्रेड (उपयोग करना ) सेट कर रहा था और उस लूपर के लिए हैंडलर के साथ कंस्ट्रक्टर को एक उदाहरण दे रहा था । आपके पास दूसरे लूपर का ओवरहेड है लेकिन मुख्य धागे से दूर रहेंRequestRequestQueueLooper.prepareLooper(); Looper.loop()ExecutorDeliveryRequestQueue
स्टीफन जेम्स हैंड

1

मैं उस प्रभाव को प्राप्त करने के लिए एक लॉक का उपयोग करता हूं अब सोच रहा हूं कि क्या इसका तरीका सही है जो कोई भी टिप्पणी करना चाहता है?

// as a field of the class where i wan't to do the synchronous `volley` call   
Object mLock = new Object();


// need to have the error and success listeners notifyin
final boolean[] finished = {false};
            Response.Listener<ArrayList<Integer>> responseListener = new Response.Listener<ArrayList<Integer>>() {
                @Override
                public void onResponse(ArrayList<Integer> response) {
                    synchronized (mLock) {
                        System.out.println();
                        finished[0] = true;
                        mLock.notify();

                    }


                }
            };

            Response.ErrorListener errorListener = new Response.ErrorListener() {
                @Override
                public void onErrorResponse(VolleyError error) {
                    synchronized (mLock) {
                        System.out.println();
                        finished[0] = true;
                        System.out.println();
                        mLock.notify();
                    }
                }
            };

// after adding the Request to the volley queue
synchronized (mLock) {
            try {
                while(!finished[0]) {
                    mLock.wait();
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

मुझे लगता है कि आप अनिवार्य रूप से लागू कर रहे हैं जो वॉली पहले से ही प्रदान करता है जब आप "वायदा" का उपयोग करते हैं।
स्पाकार्की 21

1
मैं catch (InterruptedException e)समय लूप के अंदर होने की सलाह दूंगा। अन्यथा धागा किसी कारण से बाधित होने पर प्रतीक्षा करने में विफल होगा
jayeffkay

@ Ajeffkay मैं पहले से ही अपवाद को पकड़ रहा हूं अगर ** InterruptedException ** होता है जबकि लूप पकड़ता है।
फोर्सविल्ल

1

मैं मैथ्यू के स्वीकृत जवाब में कुछ जोड़ना चाहता हूं। जबकि RequestFutureआपके द्वारा बनाए गए धागे से एक तुल्यकालिक कॉल करने के लिए प्रतीत हो सकता है, यह नहीं करता है। इसके बजाय, कॉल को पृष्ठभूमि थ्रेड पर निष्पादित किया जाता है।

पुस्तकालय से जाने के बाद मैं जो कुछ समझता हूं, RequestQueueउसके start()तरीके में अनुरोध भेजे जाते हैं :

    public void start() {
        ....
        mCacheDispatcher = new CacheDispatcher(...);
        mCacheDispatcher.start();
        ....
           NetworkDispatcher networkDispatcher = new NetworkDispatcher(...);
           networkDispatcher.start();
        ....
    }

अब दोनों CacheDispatcherऔर NetworkDispatcherकक्षाएं धागे का विस्तार करती हैं। इतने प्रभावी ढंग से अनुरोध कतार को समाप्त करने के लिए एक नया कार्यकर्ता धागा पैदा किया जाता है और प्रतिक्रिया को सफलता और त्रुटि श्रोताओं द्वारा आंतरिक रूप से कार्यान्वित किया जाता है RequestFuture

यद्यपि आपका दूसरा उद्देश्य प्राप्त हो गया है, लेकिन आप पहला उद्देश्य यह नहीं है कि एक नया धागा हमेशा पैदा होता है, चाहे आप जिस भी धागे से निष्पादित करते हैं RequestFuture

संक्षेप में, डिफ़ॉल्ट वॉली लाइब्रेरी के साथ सच्चा सिंक्रोनस अनुरोध संभव नहीं है। अगर मैं ग़लत हूं तो मेरी गलती सुझाएं।


0

आप वॉली के साथ सिंक अनुरोध कर सकते हैं लेकिन आपको अलग-अलग थ्रेड में विधि को कॉल करना होगा या आपका रनिंग ऐप ब्लॉक हो जाएगा, यह इस तरह होना चाहिए:

public String syncCall(){

    String URL = "http://192.168.1.35:8092/rest";
    String response = new String();



    RequestQueue requestQueue = Volley.newRequestQueue(this.getContext());

    RequestFuture<JSONObject> future = RequestFuture.newFuture();
    JsonObjectRequest request = new JsonObjectRequest(Request.Method.GET, URL, new JSONObject(), future, future);
    requestQueue.add(request);

    try {
        response = future.get().toString();
    } catch (InterruptedException e) {
        e.printStackTrace();
    } catch (ExecutionException e) {
        e.printStackTrace();
    } catch (JSONException e) {
        e.printStackTrace();
    }

    return response;


}

उसके बाद आप थ्रेड में विधि को कॉल कर सकते हैं:

 Thread thread = new Thread(new Runnable() {
                                    @Override
                                    public void run() {

                                        String response = syncCall();

                                    }
                                });
                                thread.start();
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.