অন্য এসিঙ্ক পদ্ধতির পরিবর্তে কোনও ইভেন্টের জন্য অপেক্ষা করা কি সম্ভব?


156

আমার সি # / এক্সএএমএল মেট্রো অ্যাপ্লিকেশনে, একটি বোতাম রয়েছে যা দীর্ঘ-চলমান প্রক্রিয়াটিকে সরিয়ে দেয়। সুতরাং, প্রস্তাবিত হিসাবে, আমি ইউআই থ্রেডটি ব্লক না হয়ে গেছে তা নিশ্চিত করতে আমি 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;

পোলিংটি স্পষ্টতই ভয়ানক (চক্রের অপেক্ষায় / অপচয়ে ব্যস্ত) এবং আমি ইভেন্ট-ভিত্তিক কিছু খুঁজছি।

কোন ধারনা?

এই সরল উদাহরণে বিটিডব্লিউ, একটি সমাধান অবশ্যই গেটআরসাল্টস () কে দুটি ভাগে বিভক্ত করা, প্রথম অংশটি স্টার্ট বোতাম থেকে এবং দ্বিতীয় অংশটি চালিয়ে যাওয়া বোতাম থেকে প্রার্থনা করবে। বাস্তবে, গেটআরলস্টগুলিতে ঘটে যাওয়া স্টাফগুলি আরও জটিল এবং বিভিন্ন ধরণের ব্যবহারকারীর ইনপুট কার্যকর করতে পারে different সুতরাং একাধিক পদ্ধতিতে যুক্তি ভাঙ্গা অ-তুচ্ছ হবে-

উত্তর:


225

আপনি সেমফোরস্লিম ক্লাসের উদাহরণটি একটি সংকেত হিসাবে ব্যবহার করতে পারেন :

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

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

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

বিকল্পভাবে, আপনি একটি টাস্ক <T> তৈরি করতে টাস্ক কমপ্লিশন সোর্স <টি> ক্লাসের একটি উদাহরণ ব্যবহার করতে পারেন যা বোতামের ক্লিকের ফলাফলকে উপস্থাপন করে:

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

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

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

7
@ ড্যানিয়েলহিলগার্থ ManualResetEvent(Slim)সমর্থন করে বলে মনে হচ্ছে না WaitAsync()
এসভিক

3
@ ড্যানিয়েলহিলগার্থ না, আপনি পারেননি। asyncএর অর্থ এই নয় যে "ভিন্ন থ্রেডে চলে", বা এরকম কিছু। এটির অর্থ কেবলমাত্র "আপনি awaitএই পদ্ধতিতে ব্যবহার করতে পারেন "। এবং এই ক্ষেত্রে, ভিতরে GetResults()ব্লক করা আসলে ইউআই থ্রেডকে ব্লক করে দেবে।
এসভিক

2
@Gabe awaitনিজেই guaranted না যে অন্য থ্রেড তৈরি করা হয়, কিন্তু এটি অন্য সব কিছুর কারণ পরে একটি ধারাবাহিকতা হিসাবে চালানোর জন্য বিবৃতি Taskবা awaitable যে আপনাকে কল awaitকরে। প্রায়ই আরো বেশী না, এটা কিছু অ্যাসিঙ্ক্রোনাস অপারেশন সাজানোর, যা আই সমাপ্তির, অথবা এমন কিছু বিষয় যা হতে পারে হল অন্য থ্রেডে।
ক্যাস্পারওন

16
+1 টি। আমাকে এটি সন্ধান করতে হয়েছিল, তাই অন্যরা আগ্রহী এমন ক্ষেত্রে: SemaphoreSlim.WaitAsyncকেবল Waitএকটি থ্রেড পুলের থ্রেডে চাপ দেয় না । SemaphoreSlimএর যথাযথ সারি রয়েছে Taskযা প্রয়োগ করতে ব্যবহৃত হয় WaitAsync
স্টিফেন ক্লিয়ারি

14
TaskCompletionSource <T> + প্রতীক্ষা। টাস্ক + .সেটরসাল্ট () আমার দৃশ্যের নিখুঁত সমাধান হিসাবে প্রমাণিত হয়েছে - ধন্যবাদ! :-)
সর্বোচ্চ

75

আপনার যখন কোনও অস্বাভাবিক জিনিসটি দরকার 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মতো async ManualResetEvent, আপনি কেবল ইভেন্টটি একবার "সেট" করতে পারবেন এবং ইভেন্টটির একটি "ফলাফল" থাকতে পারে (এই ক্ষেত্রে আমরা এটি ব্যবহার করছি না, সুতরাং আমরা ফলাফলটি কেবল সেট করেছিলাম null)।


5
যেহেতু আমি "কোনও ইভেন্টের জন্য অপেক্ষা করি" মূলত একইভাবে একই পরিস্থিতিতে 'কোনও কার্যে ইএপ মোড়ানো', তাই আমি অবশ্যই এই পদ্ধতিকে পছন্দ করব। আইএমএইচও, এটি অবশ্যই সহজ / সহজ-কারণে-কারণ-সংক্রান্ত কোড।
জেমস ম্যানিং

8

আমি যে ইউটিলিটি ক্লাসটি ব্যবহার করি তা এখানে:

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; }
}

এবং এখানে আমি এটি কীভাবে ব্যবহার করব:

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

// ... make it change ...

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

1
আমি জানি না এটি কীভাবে কাজ করে। কীভাবে শোনার পদ্ধতিটি আমার কাস্টম হ্যান্ডলারটিকে অবিচ্ছিন্নভাবে সম্পাদন করছে? new Task(() => { });তাত্ক্ষণিকভাবে সম্পন্ন হবে না ?
নওফাল

5

সাধারণ সহায়ক শ্রেণি:

public class EventAwaiter<TEventArgs>
{
    private readonly TaskCompletionSource<TEventArgs> _eventArrived = new TaskCompletionSource<TEventArgs>();

    private readonly Action<EventHandler<TEventArgs>> _unsubscribe;

    public EventAwaiter(Action<EventHandler<TEventArgs>> subscribe, Action<EventHandler<TEventArgs>> unsubscribe)
    {
        subscribe(Subscription);
        _unsubscribe = unsubscribe;
    }

    public Task<TEventArgs> Task => _eventArrived.Task;

    private EventHandler<TEventArgs> Subscription => (s, e) =>
        {
            _eventArrived.TrySetResult(e);
            _unsubscribe(Subscription);
        };
}

ব্যবহার:

var valueChangedEventAwaiter = new EventAwaiter<YourEventArgs>(
                            h => example.YourEvent += h,
                            h => example.YourEvent -= h);
await valueChangedEventAwaiter.Task;

1
আপনি কীভাবে সাবস্ক্রিপশনটি ক্লিনআপ করবেন example.YourEvent?
ডেনিস পি

@ ডেনিসপি সম্ভবত ইভেন্টএওটারের কনস্ট্রাক্টরের ইভেন্টটি পাস করবেন?
সিজেব্রেউ

@ ডেনিসপি আমি সংস্করণটি উন্নত করে একটি সংক্ষিপ্ত পরীক্ষা চালিয়েছি।
ফেলিক্স কেইল

আমি পরিস্থিতিগুলির উপর নির্ভর করে আইডিস্পোজেবলও যুক্ত দেখতে পাচ্ছিলাম। এছাড়াও, ইভেন্টটি দু'বার টাইপ না করা, আমরা ইভেন্টের নামটি পাস করার জন্য প্রতিচ্ছবিটিও ব্যবহার করতে পারি, সুতরাং ব্যবহার আরও সহজ is অন্যথায়, আমি প্যাটার্নটি পছন্দ করি, আপনাকে ধন্যবাদ।
ডেনিস পি

4

আদর্শভাবে, আপনি না । আপনি যখন অ্যাসিঙ্ক থ্রেডটি অবরুদ্ধ করতে পারেন তবে এটি সম্পদের অপচয় এবং আদর্শ নয়।

ক্যানোনিকাল উদাহরণটি বিবেচনা করুন যেখানে বোতামটি ক্লিক করার অপেক্ষায় ব্যবহারকারী লাঞ্চ করতে যায় to

ব্যবহারকারীর কাছ থেকে ইনপুটটির জন্য অপেক্ষা করতে যদি আপনি আপনার অ্যাসিক্রোনাস কোডটি থামিয়ে রেখেছেন, তবে সেই থ্রেডটি বিরতি দেওয়া অবস্থায় এটি কেবল সংস্থানগুলি নষ্ট করে।

এটি বলেছিল, আপনার অ্যাসিক্রোনাস অপারেশনে, আপনি সেই অবস্থাটি সেট করে রেখেছেন যে আপনি যে পয়েন্টটি বোতামটি সক্রিয় আছে সেদিকেই ধরে রাখতে হবে এবং আপনি একটি ক্লিকে "অপেক্ষা" করছেন waiting এই মুহুর্তে, আপনার GetResultsপদ্ধতি বন্ধ হয়ে যায়

তারপর যখন বোতাম হয় ক্লিক করেন, রাষ্ট্রীয় যে আপনি সংরক্ষণ উপর ভিত্তি করে, আপনি শুরু অন্য অ্যাসিঙ্ক্রোনাস টাস্ক কর্মকান্ড অব্যাহত।

যেহেতু SynchronizationContextকল করে ইভেন্ট হ্যান্ডলারে ক্যাপচার করা হবে GetResults(সংকলক awaitকীওয়ার্ডটি ব্যবহৃত হচ্ছে ব্যবহার করার ফলে এটি করবে এবং সিঙ্ক্রোনাইজেশন কনটেক্সট.কেনটর নন-হওয়া উচিত, আপনি কোনও ইউআই অ্যাপ্লিকেশনটিতে রয়েছেন), আপনি ব্যবহার 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আপনার বাটনটি চাপ দেওয়া ইভেন্টে ফলাফল পাওয়া অব্যাহত এমন পদ্ধতি। যদি আপনার বোতামটি চাপ না দেওয়া হয় তবে আপনার ইভেন্ট হ্যান্ডলারটি কিছুই করে না।


কী অ্যাসিঙ্ক থ্রেড? মূল প্রশ্ন এবং আপনার উত্তর উভয়ই এমন কোনও কোড নেই যা ইউআই থ্রেডে চলবে না
এসভিক

পছন্দ করুন GetResultsফেরত a Taskawaitকেবলমাত্র "টাস্কটি চালাও এবং টাস্কটি শেষ হয়ে গেলে, এর পরে কোডটি চালিয়ে যান" says একটি সিঙ্ক্রোনাইজেশন প্রসঙ্গ আছে তা প্রদত্ত, কলটি ইউআই থ্রেডে ফিরে মার্শাল করা হবে, কারণ এটি ক্যাপচারে রয়েছে awaitawaitহয় না হিসাবে একই Task.Wait(), না অন্তত হবে।
ক্যাসপারওনে

আমি এ বিষয়ে কিছু বলিনি Wait()। তবে কোডটি GetResults()এখানে ইউআই থ্রেডে চলবে, অন্য কোনও থ্রেড নেই। অন্য কথায়, হ্যাঁ, awaitমূলত আপনি যেমন বলছেন তেমন টাস্কটি চালায় তবে এখানে, সেই টাস্কটি ইউআই থ্রেডেও চালায়।
এসভিউকে

@ এসভিক এই অনুমান করার কোনও কারণ নেই যে টাস্কটি ইউআই থ্রেডের উপরে চলেছে, আপনি কেন এই অনুমান করেন? এটা সম্ভব , কিন্তু অসম্ভব। এবং কলটি দুটি পৃথক ইউআই কল, প্রযুক্তিগতভাবে, awaitতারপরে একটি এবং তারপরে কোডের পরে await, কোনও ব্লকিং নেই। বাকি কোডটি একটি ধারাবাহিকতায় ফিরে মার্শাল করা হয় এবং এর মাধ্যমে নির্ধারিত হয় SynchronizationContext
ক্যাস্পারওন

1
অন্য যারা যারা আরও দেখতে চান তাদের জন্য এখানে দেখুন: chat.stackoverflow.com/rooms/17937 - @ এসভিক এবং আমি মূলত একে অপরকে ভুল বুঝেছি, কিন্তু একই কথা বলছিলাম।
ক্যাস্পারওন

3

স্টিফেন Toub এই প্রকাশিত AsyncManualResetEventবর্গ তার ব্লগে

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; 
        } 
    } 
}

0

সঙ্গে প্রতিক্রিয়াশীল এক্সটেনশানগুলি (Rx.Net)

var eventObservable = Observable
            .FromEventPattern<EventArgs>(
                h => example.YourEvent += h,
                h => example.YourEvent -= h);

var res = await eventObservable.FirstAsync();

আপনি নিউজ প্যাকেজ সিস্টেমের সাথে আরএক্স যুক্ত করতে পারেন eআর্যাকটিভ

নমুনা পরীক্ষিত:

    private static event EventHandler<EventArgs> _testEvent;

    private static async Task Main()
    {
        var eventObservable = Observable
            .FromEventPattern<EventArgs>(
                h => _testEvent += h,
                h => _testEvent -= h);

        Task.Delay(5000).ContinueWith(_ => _testEvent?.Invoke(null, new EventArgs()));

        var res = await eventObservable.FirstAsync();

        Console.WriteLine("Event got fired");
    }

0

আমি প্রত্যাশিত ইভেন্টগুলির জন্য আমার নিজস্ব অ্যাসিচেনভেস্ট ক্লাস ব্যবহার করছি।

public delegate Task AsyncEventHandler<T>(object sender, T args) where T : EventArgs;

public class AsyncEvent : AsyncEvent<EventArgs>
{
    public AsyncEvent() : base()
    {
    }
}

public class AsyncEvent<T> where T : EventArgs
{
    private readonly HashSet<AsyncEventHandler<T>> _handlers;

    public AsyncEvent()
    {
        _handlers = new HashSet<AsyncEventHandler<T>>();
    }

    public void Add(AsyncEventHandler<T> handler)
    {
        _handlers.Add(handler);
    }

    public void Remove(AsyncEventHandler<T> handler)
    {
        _handlers.Remove(handler);
    }

    public async Task InvokeAsync(object sender, T args)
    {
        foreach (var handler in _handlers)
        {
            await handler(sender, args);
        }
    }

    public static AsyncEvent<T> operator+(AsyncEvent<T> left, AsyncEventHandler<T> right)
    {
        var result = left ?? new AsyncEvent<T>();
        result.Add(right);
        return result;
    }

    public static AsyncEvent<T> operator-(AsyncEvent<T> left, AsyncEventHandler<T> right)
    {
        left.Remove(right);
        return left;
    }
}

ক্লাসে এমন একটি ইভেন্ট ঘোষণা করার জন্য যা ঘটনা উত্থাপন করে:

public AsyncEvent MyNormalEvent;
public AsyncEvent<ProgressEventArgs> MyCustomEvent;

ঘটনা উত্থাপন:

if (MyNormalEvent != null) await MyNormalEvent.InvokeAsync(this, new EventArgs());
if (MyCustomEvent != null) await MyCustomEvent.InvokeAsync(this, new ProgressEventArgs());

ইভেন্টগুলিতে সাবস্ক্রাইব করতে:

MyControl.Click += async (sender, args) => {
    // await...
}

MyControl.Click += (sender, args) => {
    // synchronous code
    return Task.CompletedTask;
}

1
আপনি সম্পূর্ণরূপে একটি নতুন ইভেন্ট হ্যান্ডলার প্রক্রিয়া আবিষ্কার করেছেন। .NET এর প্রতিনিধিদের সম্ভবত এটি অনুবাদ করা হবে শেষ পর্যন্ত, তবে মানুষ এটি গ্রহণ করবে বলে আশা করতে পারে না। প্রতিনিধিটির জন্য (ইভেন্টের) রিটার্নের ধরণটি নিজেই লোকদের শুরু করতে পারে। তবে ভাল প্রচেষ্টা, সত্যিই এটি ভালভাবে করা ভালো।
নওফাল

@ নওফাল ধন্যবাদ! কোনও প্রতিনিধি ফিরে না এড়াতে আমি এটিকে পরিবর্তন করেছি। উৎস পাওয়া যায় এখানে লারা ওয়েব ইঞ্জিন, Blazor বিকল্প অংশ হিসেবে।
cat_in_hat
আমাদের সাইট ব্যবহার করে, আপনি স্বীকার করেছেন যে আপনি আমাদের কুকি নীতি এবং গোপনীয়তা নীতিটি পড়েছেন এবং বুঝতে পেরেছেন ।
Licensed under cc by-sa 3.0 with attribution required.