लूप में वेट का उपयोग कैसे करें


86

मैं एक अतुल्यकालिक कंसोल ऐप बनाने की कोशिश कर रहा हूं जो एक संग्रह पर कुछ काम करता है। मेरे पास एक संस्करण है जो लूप के लिए समानांतर का उपयोग करता है एक और संस्करण जो एसिंक्स / वेट का उपयोग करता है। मुझे उम्मीद है कि समान्तर / प्रतीक्षित संस्करण समानांतर संस्करण के समान काम करेगा लेकिन यह समकालिक रूप से निष्पादित होता है। मैं क्या गलत कर रहा हूं?

class Program
{
    static void Main(string[] args)
    {
        var worker = new Worker();
        worker.ParallelInit();
        var t = worker.Init();
        t.Wait();
        Console.ReadKey();
    }
}

public class Worker
{
    public async Task<bool> Init()
    {
        var series = Enumerable.Range(1, 5).ToList();
        foreach (var i in series)
        {
            Console.WriteLine("Starting Process {0}", i);
            var result = await DoWorkAsync(i);
            if (result)
            {
                Console.WriteLine("Ending Process {0}", i);
            }
        }

        return true;
    }

    public async Task<bool> DoWorkAsync(int i)
    {
        Console.WriteLine("working..{0}", i);
        await Task.Delay(1000);
        return true;
    }

    public bool ParallelInit()
    {
        var series = Enumerable.Range(1, 5).ToList();
        Parallel.ForEach(series, i =>
        {
            Console.WriteLine("Starting Process {0}", i);
            DoWorkAsync(i);
            Console.WriteLine("Ending Process {0}", i);
        });
        return true;
    }
}

जवाबों:


123

जिस तरह से आप awaitकीवर्ड का उपयोग कर रहे हैं वह C # को बताता है कि आप हर बार लूप से गुजरने का इंतजार करना चाहते हैं, जो समानांतर नहीं है। आप अपनी पद्धति को इस तरह से फिर से लिख सकते हैं कि आप क्या चाहते हैं, Taskएस की सूची को संग्रहीत करके और फिर awaitउन सभी के साथ संयुक्त करें Task.WhenAll

public async Task<bool> Init()
{
    var series = Enumerable.Range(1, 5).ToList();
    var tasks = new List<Task<Tuple<int, bool>>>();
    foreach (var i in series)
    {
        Console.WriteLine("Starting Process {0}", i);
        tasks.Add(DoWorkAsync(i));
    }
    foreach (var task in await Task.WhenAll(tasks))
    {
        if (task.Item2)
        {
            Console.WriteLine("Ending Process {0}", task.Item1);
        }
    }
    return true;
}

public async Task<Tuple<int, bool>> DoWorkAsync(int i)
{
    Console.WriteLine("working..{0}", i);
    await Task.Delay(1000);
    return Tuple.Create(i, true);
}

3
मैं दूसरों के बारे में नहीं जानता, लेकिन समानांतर छोरों के लिए एक समानांतर / फॉरचैक अधिक सीधे आगे लगता है।
19

8
ध्यान दें कि जब आप देखते हैं कि कार्य वास्तव में समाप्त हो रहा है तो Ending Processअधिसूचना नहीं है। उन सूचनाओं के सभी सही होने के बाद क्रमिक रूप से बाहर फेंक दिया जाता है पिछले कार्यों खत्म की। जब तक आप "अंतिम प्रक्रिया 1" देखते हैं, तब तक प्रक्रिया 1 लंबे समय तक समाप्त हो सकती है। वहाँ शब्दों की पसंद के अलावा, +1।
असद सईदुद्दीन

@Brettski मैं गलत हो सकता है, लेकिन एक समानांतर लूप में किसी भी प्रकार का एसिंक्स परिणाम होता है। एक टास्क <टी> को वापस करके आप तुरंत एक टास्क ऑब्जेक्ट वापस प्राप्त करते हैं जिसमें आप उस काम को प्रबंधित कर सकते हैं जो अंदर चल रहा है जैसे कि इसे रद्द करना या अपवाद देखना। अब Async / Await के साथ आप टास्क ऑब्जेक्ट के साथ और अधिक दोस्ताना तरीके से काम कर सकते हैं - यही कि आपको Task.Result नहीं करना है।
मफिन मैन

@Tim S, क्या होगा यदि मैं टास्क.जैन विधि का उपयोग करके async फ़ंक्शन के साथ मान वापस करना चाहता हूं?
मिहिर

यह एक बुरा व्यवहार एक को लागू करने के होगा Semaphoreमें DoWorkAsyncअधिकतम कार्यों के क्रियान्वयन को सीमित करने के?
C4d

39

awaitअगली पुनरावृत्ति शुरू करने से पहले आपका कोड प्रत्येक ऑपरेशन (उपयोग करने ) का इंतजार करता है ।
इसलिए, आपको कोई समानता नहीं मिलती है।

यदि आप समानांतर में एक मौजूदा अतुल्यकालिक ऑपरेशन चलाना चाहते हैं, तो आपको ज़रूरत नहीं है await; आपको बस एक संग्रह प्राप्त करने की आवश्यकता है Taskऔर Task.WhenAll()उन सभी के लिए प्रतीक्षा करने वाले कार्य को वापस करने के लिए कॉल करें:

return Task.WhenAll(list.Select(DoWorkAsync));

तो आप किसी भी छोरों में किसी भी अतुल्यकालिक तरीकों का उपयोग नहीं कर सकते?
सतीश

4
@ आतिश: आप कर सकते हैं। हालांकि, awaitआप जो चाहते हैं , उसके ठीक विपरीत है - यह खत्म होने का इंतजार करता हैTask
स्लाक्स

मैं आपका जवाब स्वीकार करना चाहता था लेकिन टिम्स एस का बेहतर जवाब है।
सतीश

या अगर आपको यह जानने की आवश्यकता नहीं है कि कार्य समाप्त होने के बाद आप केवल उनके लिए इंतजार किए बिना तरीकों को कॉल कर सकते हैं
डिस्कलॉग

यह पुष्टि करने के लिए कि सिंटैक्स क्या कर रहा है - यह DoWorkAsyncप्रत्येक आइटम पर कॉल की गई टास्क को चला रहा है list(प्रत्येक आइटम को पास करना DoWorkAsync, जिसमें मुझे लगता है कि एक एकल पैरामीटर है)?
jbyrd


4

C # 7.0 में, आप प्रत्येक सदस्य के नाम का उपयोग कर सकते हैं , यहाँ नया वाक्यविन्यास का उपयोग करते हुए टिम एस का उत्तर दिया गया है:

public async Task<bool> Init()
{
    var series = Enumerable.Range(1, 5).ToList();
    var tasks = new List<Task<(int Index, bool IsDone)>>();

    foreach (var i in series)
    {
        Console.WriteLine("Starting Process {0}", i);
        tasks.Add(DoWorkAsync(i));
    }

    foreach (var task in await Task.WhenAll(tasks))
    {
        if (task.IsDone)
        {
            Console.WriteLine("Ending Process {0}", task.Index);
        }
    }

    return true;
}

public async Task<(int Index, bool IsDone)> DoWorkAsync(int i)
{
    Console.WriteLine("working..{0}", i);
    await Task.Delay(1000);
    return (i, true);
}

आप task. अंदर सेforeach छुटकारा भी पा सकते हैं :

// ...
foreach (var (IsDone, Index) in await Task.WhenAll(tasks))
{
    if (IsDone)
    {
        Console.WriteLine("Ending Process {0}", Index);
    }
}
// ...
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.