2012-10-12 14 views
108

मेरे सी #/एक्सएएमएल मेट्रो ऐप में, एक बटन है जो लंबे समय तक चलने वाली प्रक्रिया को बंद कर देता है। तो, के रूप में सिफारिश, मैं async उपयोग कर रहा हूँ/यकीन है कि यूआई धागा अवरुद्ध नहीं प्राप्त करता है बनाने के लिए इंतजार है:क्या किसी अन्य एसिंक विधि की बजाय किसी ईवेंट का इंतजार करना संभव है?

private async void Button_Click_1(object sender, RoutedEventArgs e) 
{ 
    await GetResults(); 
} 

private async Task GetResults() 
{ 
    // Do lot of complex stuff that takes a long time 
    // (e.g. contact some web services) 
    ... 
} 

कभी कभी, सामान GetResults भीतर हो रहा अतिरिक्त उपयोगकर्ता इनपुट से पहले इसे जारी रख सकते हैं की आवश्यकता होगी। सादगी के लिए, मान लें कि उपयोगकर्ता को बस "जारी रखें" बटन पर क्लिक करना होगा।

मेरा प्रश्न है: कैसे मैं इस तरह से GetResults के निष्पादन को निलंबित कर सकते हैं कि यह इस तरह के एक और बटन को क्लिक के रूप में एक घटना इंतजार कर रहा है?

यहाँ प्राप्त करने के लिए मैं क्या देख रहा हूँ एक बदसूरत तरीका है: जारी रखें "बटन एक ध्वज सेट के लिए ईवेंट हैंडलर ...

private bool _continue = false; 
private void buttonContinue_Click(object sender, RoutedEventArgs e) 
{ 
    _continue = true; 
} 

... और GetResults समय-समय पर चुनाव यह:

buttonContinue.Visibility = Visibility.Visible; 
while (!_continue) await Task.Delay(100); // poll _continue every 100ms 
buttonContinue.Visibility = Visibility.Collapsed; 

मतदान स्पष्ट रूप से भयानक है (व्यस्त प्रतीक्षा/चक्र का अपशिष्ट) और मैं कुछ घटना-आधारित के लिए देख रहा हूँ।

कोई भी विचार?

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

उत्तर

150

आप एक संकेत के रूप में SemaphoreSlim Class का एक उदाहरण का उपयोग कर सकते हैं:

private SemaphoreSlim signal = new SemaphoreSlim(0, 1); 

// set signal in event 
signal.Release(); 

// wait for signal somewhere else 
await signal.WaitAsync(); 

वैकल्पिक रूप से, आप एक Task<T> कि बटन क्लिक के परिणाम का प्रतिनिधित्व करता बनाने के लिए TaskCompletionSource<T> Class का एक उदाहरण का उपयोग कर सकते हैं:

private TaskCompletionSource<bool> tcs = new TaskCompletionSource<bool>(); 

// complete task in event 
tcs.SetResult(true); 

// wait for task somewhere else 
await tcs.Task; 
+0

मैं एक 'ManualResetEvent' इस्तेमाल किया गया होगा। क्या 'सेमफोर स्लिम' का उपयोग करने का कोई फायदा है या आप किसी एक का उपयोग कर सकते हैं? –

+5

@DanielHilgarth 'मैनुअल रीसेट (स्लिम)' 'WaitAsync()' का समर्थन नहीं करता है। – svick

+0

@svick: अच्छा बिंदु। हालांकि, 'GetResult' पहले से ही' async 'है, आप बिना किसी समस्या के इस विधि के अंदर अवरुद्ध कर सकते हैं, है ना? –

4

आदर्श रूप से, आप नहीं हैं। जबकि आप निश्चित रूप से एसिंक थ्रेड को अवरुद्ध कर सकते हैं, यह संसाधनों का अपशिष्ट है, और आदर्श नहीं है।

उस कैनोलिक उदाहरण पर विचार करें जहां उपयोगकर्ता लंच पर जाता है जबकि बटन क्लिक करने का इंतजार कर रहा है।

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

यह कहा गया है कि, अगर आपके एसिंक्रोनस ऑपरेशन में बेहतर है, तो आप इस स्थिति को सेट करते हैं कि आपको उस बिंदु पर बनाए रखने की आवश्यकता है जहां बटन सक्षम है और आप एक क्लिक पर "प्रतीक्षा" कर रहे हैं। उस बिंदु पर, आपकी GetResults विधि बंद हो जाती है।

तब, जब बटन क्लिक किया है, राज्य है कि आप संग्रहीत किया है के आधार पर है, तो आप शुरू एक और अतुल्यकालिक कार्य काम जारी रखने के लिए।

SynchronizationContext ईवेंट हैंडलर कॉल GetResults (संकलक await कीवर्ड का उपयोग करने का एक परिणाम के रूप यह कर देगा इस्तेमाल किया जा रहा है, और तथ्य यह है कि SynchronizationContext.Current में गैर-शून्य होना चाहिए में कब्जा होंगे इसलिए दिया कि तुम एक में हैं यूआई आवेदन), तुम इतनी तरह async/await उपयोग कर सकते हैं:

private async void Button_Click_1(object sender, RoutedEventArgs e) 
{ 
    await GetResults(); 

    // Show dialog/UI element. This code has been marshaled 
    // back to the UI thread because the SynchronizationContext 
    // was captured behind the scenes when 
    // await was called on the previous line. 
    ... 

    // Check continue, if true, then continue with another async task. 
    if (_continue) await ContinueToGetResultsAsync(); 
} 

private bool _continue = false; 
private void buttonContinue_Click(object sender, RoutedEventArgs e) 
{ 
    _continue = true; 
} 

private async Task GetResults() 
{ 
    // Do lot of complex stuff that takes a long time 
    // (e.g. contact some web services) 
    ... 
} 

ContinueToGetResultsAsync विधि है कि यदि आपकी बटन धकेल दिया जाता है में परिणाम प्राप्त करने के लिए जारी है। यदि आपका बटन धक्का नहीं है, तो आपका ईवेंट हैंडलर कुछ भी नहीं करता है।

+0

क्या async धागा? मूल प्रश्न में और आपके उत्तर में, कोई कोड नहीं है जो UI थ्रेड पर * नहीं * चलाएगा। – svick

+0

@svick सच नहीं है। 'GetResults' एक' कार्य 'देता है। 'प्रतीक्षा' बस कहता है "कार्य चलाएं, और जब कार्य पूरा हो जाए, तो इसके बाद कोड जारी रखें"। यह देखते हुए कि एक सिंक्रनाइज़ेशन संदर्भ है, कॉल को UI थ्रेड पर वापस मार्शल किया गया है, क्योंकि यह 'प्रतीक्षा' पर कब्जा कर लिया गया है। 'प्रतीक्षा करें * नहीं *' टास्क.एट() 'के समान है, कम से कम नहीं। – casperOne

+0

मैंने 'प्रतीक्षा()' के बारे में कुछ भी नहीं कहा। लेकिन 'GetResults() 'में कोड यूआई थ्रेड पर चलाएगा, कोई अन्य धागा नहीं है। दूसरे शब्दों में, हां, 'प्रतीक्षा' मूल रूप से कार्य चलाता है, जैसा कि आप कहते हैं, लेकिन यहां, यह कार्य यूआई थ्रेड पर भी चलता है। – svick

54

आप एक असामान्य बात आप पर await करने की जरूरत है, तो सबसे आसान जवाब अक्सर TaskCompletionSource है (या कुछ async पर TaskCompletionSource आधारित आदिम सक्षम)।

इस मामले में, अपनी जरूरत, काफी सरल है, इसलिए आप बस सीधे TaskCompletionSource उपयोग कर सकते हैं:

private TaskCompletionSource<object> continueClicked; 

private async void Button_Click_1(object sender, RoutedEventArgs e) 
{ 
    // Note: You probably want to disable this button while "in progress" so the 
    // user can't click it twice. 
    await GetResults(); 
    // And re-enable the button here, possibly in a finally block. 
} 

private async Task GetResults() 
{ 
    // Do lot of complex stuff that takes a long time 
    // (e.g. contact some web services) 

    // Wait for the user to click Continue. 
    continueClicked = new TaskCompletionSource<object>(); 
    buttonContinue.Visibility = Visibility.Visible; 
    await continueClicked.Task; 
    buttonContinue.Visibility = Visibility.Collapsed; 

    // More work... 
} 

private void buttonContinue_Click(object sender, RoutedEventArgs e) 
{ 
    if (continueClicked != null) 
    continueClicked.TrySetResult(null); 
} 

तार्किक रूप से, TaskCompletionSource एक asyncManualResetEvent की तरह है, सिवाय इसके कि आप केवल घटना "सेट" कर सकते हैं कि एक बार और घटना में "परिणाम" हो सकता है (इस मामले में, हम इसका उपयोग नहीं कर रहे हैं, इसलिए हमने परिणाम को null पर सेट किया है)।

+5

चूंकि मैं "एक घटना का इंतजार" करता हूं क्योंकि मूल रूप से उसी स्थिति में 'एक कार्य में लपेटें' के रूप में, मैं निश्चित रूप से इस दृष्टिकोण को पसंद करूंगा। आईएमएचओ, यह कोड के बारे में निश्चित रूप से सरल/आसान कारण है। –

2

स्टीफन टब ने इस AsyncManualResetEvent कक्षा on his blog प्रकाशित किया।

public class AsyncEventListener 
{ 
    private readonly Func<bool> _predicate; 

    public AsyncEventListener() : this(() => true) 
    { 

    } 

    public AsyncEventListener(Func<bool> predicate) 
    { 
     _predicate = predicate; 
     Successfully = new Task(() => { }); 
    } 

    public void Listen(object sender, EventArgs eventArgs) 
    { 
     if (!Successfully.IsCompleted && _predicate.Invoke()) 
     { 
      Successfully.RunSynchronously(); 
     } 
    } 

    public Task Successfully { get; } 
} 

यहाँ और मैं इसे कैसे उपयोग करते हैं::

public class AsyncManualResetEvent 
{ 
    private volatile TaskCompletionSource<bool> m_tcs = new TaskCompletionSource<bool>(); 

    public Task WaitAsync() { return m_tcs.Task; } 

    public void Set() 
    { 
     var tcs = m_tcs; 
     Task.Factory.StartNew(s => ((TaskCompletionSource<bool>)s).TrySetResult(true), 
      tcs, CancellationToken.None, TaskCreationOptions.PreferFairness, TaskScheduler.Default); 
     tcs.Task.Wait(); 
    } 

    public void Reset() 
    { 
     while (true) 
     { 
      var tcs = m_tcs; 
      if (!tcs.Task.IsCompleted || 
       Interlocked.CompareExchange(ref m_tcs, new TaskCompletionSource<bool>(), tcs) == tcs) 
       return; 
     } 
    } 
} 
3

यहाँ एक उपयोगिता वर्ग है कि मैं का उपयोग करें

var itChanged = new AsyncEventListener(); 
someObject.PropertyChanged += itChanged.Listen; 

// ... make it change ... 

await itChanged.Successfully; 
someObject.PropertyChanged -= itChanged.Listen; 
0

सरल सहायक वर्ग:

public class EventAwaiter<TEventArgs> 
{ 
    #region Fields 

    private TaskCompletionSource<TEventArgs> _eventArrived = new TaskCompletionSource<TEventArgs>(); 

    #endregion Fields 

    #region Properties 

    public Task<TEventArgs> Task { get; set; } 

    public EventHandler<TEventArgs> Subscription => (s, e) => _eventArrived.TrySetResult(e); 

    #endregion Properties 
} 

उपयोग:

var valueChangedEventAwaiter = new EventAwaiter<YourEventArgs>(); 
example.YourEvent += valueChangedEventAwaiter.Subscription; 
await valueChangedEventAwaiter.Task; 
+0

आप 'example.YourEvent' की सदस्यता को कैसे साफ करेंगे? –

संबंधित मुद्दे