কখনও শেষ না হওয়া কাজটি কার্যকর করার সঠিক উপায়। (টাইমার বনাম টাস্ক)


92

সুতরাং, যতক্ষণ অ্যাপ্লিকেশন চলছে বা বাতিলকরণের জন্য অনুরোধ করা হচ্ছে ততক্ষণ আমার অ্যাপ্লিকেশনটিকে প্রায় ক্রমাগত (10 সেকেন্ডের বিরতি দিয়ে বা প্রতিটি রানের মধ্যে) ক্রিয়া সম্পাদন করা উচিত। এটি যে কাজটি করতে হবে তাতে 30 সেকেন্ড সময় নেওয়ার সম্ভাবনা রয়েছে।

একটি সিস্টেম.টাইমার্স.টাইমারের ব্যবহার করা কি পূর্ববর্তী "টিক" সম্পন্ন হওয়ার আগে এটি ক্রিয়াটি সম্পাদন করে না তা নিশ্চিত করার জন্য অটোরিসেট ব্যবহার করা আরও ভাল।

বা আমি লংআরনিং মোডে একটি বাতিল টোকেন সহ একটি সাধারণ টাস্কটি ব্যবহার করব এবং 10 সেকেন্ডের থ্রেড দিয়ে কাজটি অ্যাকশনটিকে কল করে এর ভিতরে লুপটি বসাতে নিয়মিত অসীম করবো? কলগুলির মধ্যে ঘুমাও? Async / অপেক্ষার মডেল হিসাবে, আমি নিশ্চিত না যে এখানে কাজ থেকে কোনও রিটার্ন মান না থাকায় এটি এখানে উপযুক্ত হবে।

CancellationTokenSource wtoken;
Task task;

void StopWork()
{
    wtoken.Cancel();

    try 
    {
        task.Wait();
    } catch(AggregateException) { }
}

void StartWork()
{
    wtoken = new CancellationTokenSource();

    task = Task.Factory.StartNew(() =>
    {
        while (true)
        {
            wtoken.Token.ThrowIfCancellationRequested();
            DoWork();
            Thread.Sleep(10000);
        }
    }, wtoken, TaskCreationOptions.LongRunning);
}

void DoWork()
{
    // Some work that takes up to 30 seconds but isn't returning anything.
}

অথবা এর অটোসেট সম্পত্তি ব্যবহার করার সময় একটি সাধারণ টাইমার ব্যবহার করুন এবং এটি বাতিল করার জন্য .Sop () কল করুন?


আপনি কী অর্জন করতে চাইছেন তা বিবেচনা করে কার্যকে ওভারকিলের মতো মনে হচ্ছে। en.wikedia.org/wiki/KISS_prصول । অনটিক () এর শুরুতে টাইমার বন্ধ করুন, আপনার কিছু না করা, কাজ করা, টাইমার পুনরায় আরম্ভ করা উচিত কিনা তা দেখার জন্য একটি বুল পরীক্ষা করুন।
মাইক ট্রুসভ

উত্তর:


94

আমি এটির জন্য টিপিএল ডেটাফ্লো ব্যবহার করব (যেহেতু আপনি .NET 4.5 ব্যবহার করছেন এবং এটি Taskঅভ্যন্তরীণভাবে ব্যবহার করে)। ActionBlock<TInput>এটি ক্রিয়া প্রক্রিয়াজাত হওয়ার পরে এবং উপযুক্ত সময়ের জন্য অপেক্ষা করার পরে কোন আইটেম নিজের কাছে পোস্ট করে আপনি সহজেই এটি তৈরি করতে পারেন ।

প্রথমে এমন একটি কারখানা তৈরি করুন যা আপনার কখনও শেষ না হওয়া কাজটি তৈরি করবে:

ITargetBlock<DateTimeOffset> CreateNeverEndingTask(
    Action<DateTimeOffset> action, CancellationToken cancellationToken)
{
    // Validate parameters.
    if (action == null) throw new ArgumentNullException("action");

    // Declare the block variable, it needs to be captured.
    ActionBlock<DateTimeOffset> block = null;

    // Create the block, it will call itself, so
    // you need to separate the declaration and
    // the assignment.
    // Async so you can wait easily when the
    // delay comes.
    block = new ActionBlock<DateTimeOffset>(async now => {
        // Perform the action.
        action(now);

        // Wait.
        await Task.Delay(TimeSpan.FromSeconds(10), cancellationToken).
            // Doing this here because synchronization context more than
            // likely *doesn't* need to be captured for the continuation
            // here.  As a matter of fact, that would be downright
            // dangerous.
            ConfigureAwait(false);

        // Post the action back to the block.
        block.Post(DateTimeOffset.Now);
    }, new ExecutionDataflowBlockOptions { 
        CancellationToken = cancellationToken
    });

    // Return the block.
    return block;
}

আমি ActionBlock<TInput>একটি DateTimeOffsetকাঠামো নিতে বেছে নিয়েছি ; আপনাকে একটি প্রকারের প্যারামিটারটি পাস করতে হবে এবং এটি কিছু কার্যকর অবস্থাও পাস করতে পারে (আপনি চাইলে রাষ্ট্রের প্রকৃতি পরিবর্তন করতে পারেন)।

এছাড়াও, নোট করুন যে ActionBlock<TInput>ডিফল্টরূপে একবারে কেবলমাত্র একটি আইটেম প্রসেস হয় , সুতরাং আপনি গ্যারান্টিযুক্ত যে কেবলমাত্র একটি ক্রিয়া প্রক্রিয়াজাত করা হবে (অর্থাত্ যখন এটি এক্সটেনশন পদ্ধতিটি নিজেই ফিরে আসে তখন আপনাকে পুনরায় পুনঃস্থাপনের মুখোমুখি হতে হবে না )।Post

আমি CancellationTokenকাঠামোটি কনস্ট্রাক্টর ActionBlock<TInput>এবং Task.Delayপদ্ধতি কল উভয়কেই দিয়েছি ; যদি প্রক্রিয়াটি বাতিল হয় তবে বাতিলকরণটি প্রথম সম্ভাব্য সুযোগে হবে।

সেখান থেকে কার্যকর করা ITargetBlock<DateTimeoffset>ইন্টারফেসটি সংরক্ষণ করার জন্য আপনার কোডটির একটি সহজ রিফ্যাকচারিং ActionBlock<TInput>(এটি হ'ল উচ্চ-স্তরের বিমূর্ততা যা উপভোক্তাদের ব্লকগুলি উপস্থাপন করে এবং আপনি Postএক্সটেনশন পদ্ধতিতে একটি কলের মাধ্যমে গ্রাহককে ট্রিগার করতে সক্ষম হতে চান ):

CancellationTokenSource wtoken;
ActionBlock<DateTimeOffset> task;

আপনার StartWorkপদ্ধতি:

void StartWork()
{
    // Create the token source.
    wtoken = new CancellationTokenSource();

    // Set the task.
    task = CreateNeverEndingTask(now => DoWork(), wtoken.Token);

    // Start the task.  Post the time.
    task.Post(DateTimeOffset.Now);
}

এবং তারপরে আপনার StopWorkপদ্ধতি:

void StopWork()
{
    // CancellationTokenSource implements IDisposable.
    using (wtoken)
    {
        // Cancel.  This will cancel the task.
        wtoken.Cancel();
    }

    // Set everything to null, since the references
    // are on the class level and keeping them around
    // is holding onto invalid state.
    wtoken = null;
    task = null;
}

আপনি এখানে টিপিএল ডেটাফ্লো ব্যবহার করতে চান কেন? কয়েকটি কারণ:

উদ্বেগ বিচ্ছেদ

CreateNeverEndingTaskপদ্ধতি এখন একটি কারখানা যে আপনার "পরিষেবা" তাই কথা বলতে সৃষ্টি করে। কখন এটি শুরু হয় এবং থামবে আপনি নিয়ন্ত্রণ করেন এবং এটি সম্পূর্ণ স্বনির্ভর। আপনার কোডের অন্যান্য দিকগুলির সাথে আপনাকে টাইমারের স্টেট কন্ট্রোলটি অন্তর্বর্তী করতে হবে না। আপনি কেবল এই ব্লকটি তৈরি করেন, এটি শুরু করুন এবং আপনার হয়ে গেলে এটি বন্ধ করুন।

থ্রেড / টাস্ক / রিসোর্সের আরও দক্ষ ব্যবহার

টিপিএল ডেটা প্রবাহে ব্লকগুলির জন্য ডিফল্ট সময়সূচী এটির জন্য একই Task, যা থ্রেড পুল। ActionBlock<TInput>আপনার ক্রিয়াকলাপটি প্রক্রিয়াকরণ করার জন্য, পাশাপাশি কল করার Task.Delayমাধ্যমে আপনি যখন কিছুই করছেন না তখন আপনি যে থ্রেডটি ব্যবহার করছেন সেটি নিয়ন্ত্রণ পেয়ে যাচ্ছেন। মঞ্জুর, আপনি যখন নতুনত্বের Taskধারাবাহিকতাটি প্রসেস করবেন তখন এটি কিছুটা ওভারহেডের দিকে নিয়ে যায় , তবে এটি ছোট হওয়া উচিত, বিবেচনা করে আপনি এটিকে কোনও টান লুপে প্রসেস করছেন না (আপনি অনুরোধের মধ্যে দশ সেকেন্ড অপেক্ষা করছেন)।

যদি DoWorkফাংশনটি প্রকৃতপক্ষে অপেক্ষারত করা যায় (যেমন, এটির সাথে এটি প্রত্যাবর্তন করে Task), তবে আপনি উপরের কারখানার পদ্ধতিটি টুইটার করে আরও সম্ভবত এটি অনুকূল করতে পারেন , এর মতো Func<DateTimeOffset, CancellationToken, Task>পরিবর্তে Action<DateTimeOffset>:

ITargetBlock<DateTimeOffset> CreateNeverEndingTask(
    Func<DateTimeOffset, CancellationToken, Task> action, 
    CancellationToken cancellationToken)
{
    // Validate parameters.
    if (action == null) throw new ArgumentNullException("action");

    // Declare the block variable, it needs to be captured.
    ActionBlock<DateTimeOffset> block = null;

    // Create the block, it will call itself, so
    // you need to separate the declaration and
    // the assignment.
    // Async so you can wait easily when the
    // delay comes.
    block = new ActionBlock<DateTimeOffset>(async now => {
        // Perform the action.  Wait on the result.
        await action(now, cancellationToken).
            // Doing this here because synchronization context more than
            // likely *doesn't* need to be captured for the continuation
            // here.  As a matter of fact, that would be downright
            // dangerous.
            ConfigureAwait(false);

        // Wait.
        await Task.Delay(TimeSpan.FromSeconds(10), cancellationToken).
            // Same as above.
            ConfigureAwait(false);

        // Post the action back to the block.
        block.Post(DateTimeOffset.Now);
    }, new ExecutionDataflowBlockOptions { 
        CancellationToken = cancellationToken
    });

    // Return the block.
    return block;
}

অবশ্যই, এটি CancellationTokenআপনার পদ্ধতির মাধ্যমে বুনন করা ভাল অনুশীলন হবে (যদি এটি কোনও গ্রহণ করে), যা এখানে করা হয়।

তার মানে আপনার তখন DoWorkAsyncনিম্নলিখিত স্বাক্ষর সহ একটি পদ্ধতি থাকবে :

Task DoWorkAsync(CancellationToken cancellationToken);

আপনাকে পরিবর্তিত করতে হবে (কেবলমাত্র সামান্য, এবং আপনি উদ্বেগের বিচ্ছেদ থেকে এখানে রক্তপাত করছেন না) StartWorkপদ্ধতিটিতে পাস করা নতুন স্বাক্ষরের জন্য অ্যাকাউন্টটি CreateNeverEndingTaskপদ্ধতি, যেমন:

void StartWork()
{
    // Create the token source.
    wtoken = new CancellationTokenSource();

    // Set the task.
    task = CreateNeverEndingTask((now, ct) => DoWorkAsync(ct), wtoken.Token);

    // Start the task.  Post the time.
    task.Post(DateTimeOffset.Now, wtoken.Token);
}

হ্যালো, আমি এই বাস্তবায়নটি চেষ্টা করছি তবে আমি সমস্যার মুখোমুখি। যদি আমার ডওওয়ার্ক কোনও যুক্তি না নিয়ে, টাস্ক = ক্রিয়েটনেভারএন্ডিংটাস্ক (এখন => ডওর্ক (), wtoken.Token); আমাকে একটি বিল্ড ত্রুটি দেয় (টাইপ না মেলে)। অন্যদিকে, যদি আমার ডওওয়ার্ক একটি ডেটটাইমঅফসেট প্যারামিটার নেয়, তবে একই লাইনটি আমাকে একটি ভিন্ন বিল্ড ত্রুটি দেয়, আমাকে বলে যে ডওকার্কের জন্য কোনও ওভারলোড 0 টি আর্গুমেন্ট নেয় না। আপনি কি দয়া করে আমাকে এটি নির্ণয় করতে সহায়তা করবেন?
বোভাজ

4
প্রকৃতপক্ষে, আমি আমার কাজটি লাইনটিতে একটি কাস্ট যোগ করে ডুওর্ককে প্যারামিটারটি পাস করে আমার সমস্যাটি সমাধান করেছি: টাস্ক = (অ্যাকশনব্লক <ডেটটাইমঅফসেট>) ক্রিয়েটনেভারএন্ডিংটাস্ক (এখন => ডওর্ক (এখন), wtoken.Token);
বোভাজ

আপনি "অ্যাকশনব্লক <ডেটটাইমঅফসেট> টাস্ক" এর ধরণও পরিবর্তন করতে পারেন; ITargetBlock <ডেটটাইমঅফসেট> কার্যে;
এক্সঅর

4
আমি বিশ্বাস করি এটি সম্ভবত চিরকালের জন্য স্মৃতি বরাদ্দ করবে, ফলস্বরূপ এটি একটি ওভারফ্লোতে পরিচালিত করবে।
নাট গার্ডনার

@ নাটগার্ডনার কোন অংশে?
CasperOne

75

টাইমার ক্লাস ব্যবহারের চেয়েও সহজ - এই জাতীয় জিনিসগুলি করার জন্য আমি নতুন টাস্ক-ভিত্তিক ইন্টারফেসটিকে খুব সহজ বলে মনে করি।

কিছু ছোট সামঞ্জস্য রয়েছে যা আপনি নিজের উদাহরণে করতে পারেন। পরিবর্তে:

task = Task.Factory.StartNew(() =>
{
    while (true)
    {
        wtoken.Token.ThrowIfCancellationRequested();
        DoWork();
        Thread.Sleep(10000);
    }
}, wtoken, TaskCreationOptions.LongRunning);

তুমি এটি করতে পারো:

task = Task.Run(async () =>  // <- marked async
{
    while (true)
    {
        DoWork();
        await Task.Delay(10000, wtoken.Token); // <- await with cancellation
    }
}, wtoken.Token);

এই পদ্ধতি বাতিলের ভিতরে যদি তাত্ক্ষণিকভাবে ঘটবে Task.Delay, বরং জন্য অপেক্ষা করতে থাকার চেয়ে Thread.Sleepশেষ করতে।

এছাড়াও, Task.Delayওভার ব্যবহারের Thread.Sleepঅর্থ আপনি ঘুমের সময়কালে কোনও থ্রেড বেঁধে রাখছেন না।

আপনি যদি সক্ষম হন তবে আপনি DoWork()একটি বাতিল টোকেন গ্রহণ করতেও পারেন , এবং বাতিলকরণটি আরও বেশি প্রতিক্রিয়াশীল হবে।


4
আপনি যদি টাস্ক.ফ্যাক্টরি.স্টার্টনিউয়ের প্যারামিটার হিসাবে অ্যাসিঙ্ক ল্যাম্বডা ব্যবহার করেন তবে কী কাজ পাবেন তা জানুন - ব্লগস.এমএসএনএন / বি / পিএফএক্সটিয়াম / অর্চিভ / ২০১৮/ ০১ /২০/ ২০২৯9968.এএসপিএক্স আপনি যখন টাস্কটি করেন। ); বাতিল করার অনুরোধ করা হওয়ার পরে, আপনি ভুল কাজের জন্য অপেক্ষা করবেন।
Lukas পিরক্ল

হ্যাঁ, এটি আসলে টাস্ক হওয়া উচিত now এখনই চালানো উচিত, যার সঠিক ওভারলোড রয়েছে।
ges

মতে http://blogs.msdn.com/b/pfxteam/archive/2011/10/24/10229468.aspx এটা দেখে মনে হচ্ছে Task.Runব্যবহার ব্যবহারসমূহ থ্রেড পুল, আপনার উদাহরণ তাই Task.Runপরিবর্তে Task.Factory.StartNewসঙ্গে TaskCreationOptions.LongRunningঠিক একই জিনিস করে না - LongRunningবিকল্পটি ব্যবহার করার জন্য যদি আমার টাস্কটির প্রয়োজন হয় , তবে আমি Task.Runকি আপনার দেখানো মতো ব্যবহার করতে পারব না , বা আমি কিছু হারিয়ে যাচ্ছি?
জেফ

@ লুমিরিস: অ্যাসিঙ্ক / অপেক্ষার মূল বিষয় হল এটি চালানো পুরো সময়ের জন্য একটি সুতোর সাথে বেঁধে দেওয়া এড়ানো (এখানে, বিলম্বের সময় টাস্কটি থ্রেড ব্যবহার করছে না)। সুতরাং LongRunningথ্রেডগুলি বেঁধে না রাখার লক্ষ্যটির সাথে এক ধরণের বেমানান। আপনি যদি নিজের থ্রেডে চলার গ্যারান্টি দিতে চান তবে আপনি এটি ব্যবহার করতে পারেন তবে এখানে আপনি বেশিরভাগ সময় ঘুমিয়ে থাকা একটি থ্রেড শুরু করতে যাচ্ছেন। ব্যবহারের ক্ষেত্রে কী?
porges

@ পোর্জেস পয়েন্ট নেওয়া হয়েছে। আমার ব্যবহারের ক্ষেত্রে একটি অসীম লুপ চালানো কোনও কাজ হবে, যাতে প্রতিটি পুনরাবৃত্তি একটি কাজ করবে এবং পরের পুনরাবৃত্তির উপর অন্য কাজ করার আগে 2 সেকেন্ডের জন্য 'শিথিল' করবে। এটি চিরকাল চলছে, তবে নিয়মিত 2 সেকেন্ড বিরতি নিচ্ছে। আমার মন্তব্যটি যদিও আপনাকে LongRunningএটি Task.Runসিনট্যাক্স ব্যবহার করে নির্দিষ্ট করা যায় কিনা সে সম্পর্কে আরও ছিল । ডকুমেন্টেশন থেকে মনে হচ্ছে Task.Runএটি ক্লিনার সিনট্যাক্সের মতো, যতক্ষণ না আপনি এটির ডিফল্ট সেটিংস ব্যবহার করে খুশি হন। এটির সাথে কোনও ওভারলোড বলে মনে হয় না যা একটি TaskCreationOptionsযুক্তি নেয় ।
জেফ

4

আমি এখানে যা এলাম:

  • আপনি যে কাজটি করতে চান তার সাথে পদ্ধতিটি উত্তীর্ণ NeverEndingTaskএবং ওভাররাইড করুন ExecutionCore
  • পরিবর্তন ExecutionLoopDelayMsআপনাকে লুপগুলির মধ্যে সময়কে সামঞ্জস্য করার অনুমতি দেয় যেমন আপনি যদি ব্যাক অফ অ্যালগরিদম ব্যবহার করতে চান।
  • Start/Stop টাস্ক শুরু / থামানোর জন্য একটি সিঙ্ক্রোনাস ইন্টারফেস সরবরাহ করে।
  • LongRunningমানে আপনি প্রতি একটি ডেডিকেটেড থ্রেড পাবেন NeverEndingTask
  • এই শ্রেণিটি ActionBlockউপরের ভিত্তিক সমাধানের বিপরীতে লুপে মেমরি বরাদ্দ করে না ।
  • নীচের কোডটি স্কেচ, প্রয়োজনীয়ভাবে প্রডাকশন কোড নয় :)

:

public abstract class NeverEndingTask
{
    // Using a CTS allows NeverEndingTask to "cancel itself"
    private readonly CancellationTokenSource _cts = new CancellationTokenSource();

    protected NeverEndingTask()
    {
         TheNeverEndingTask = new Task(
            () =>
            {
                // Wait to see if we get cancelled...
                while (!_cts.Token.WaitHandle.WaitOne(ExecutionLoopDelayMs))
                {
                    // Otherwise execute our code...
                    ExecutionCore(_cts.Token);
                }
                // If we were cancelled, use the idiomatic way to terminate task
                _cts.Token.ThrowIfCancellationRequested();
            },
            _cts.Token,
            TaskCreationOptions.DenyChildAttach | TaskCreationOptions.LongRunning);

        // Do not forget to observe faulted tasks - for NeverEndingTask faults are probably never desirable
        TheNeverEndingTask.ContinueWith(x =>
        {
            Trace.TraceError(x.Exception.InnerException.Message);
            // Log/Fire Events etc.
        }, TaskContinuationOptions.OnlyOnFaulted);

    }

    protected readonly int ExecutionLoopDelayMs = 0;
    protected Task TheNeverEndingTask;

    public void Start()
    {
       // Should throw if you try to start twice...
       TheNeverEndingTask.Start();
    }

    protected abstract void ExecutionCore(CancellationToken cancellationToken);

    public void Stop()
    {
        // This code should be reentrant...
        _cts.Cancel();
        TheNeverEndingTask.Wait();
    }
}
আমাদের সাইট ব্যবহার করে, আপনি স্বীকার করেছেন যে আপনি আমাদের কুকি নীতি এবং গোপনীয়তা নীতিটি পড়েছেন এবং বুঝতে পেরেছেন ।
Licensed under cc by-sa 3.0 with attribution required.