2014-02-07 2 views
3

프레임 워크에서 비동기 적으로 대리인 대기열을 실행할 수있는 항목이 있습니까?비동기 실행을위한 대기열 작업/대리인

내가 의미하는 바는 위임자가 대기열에있는 순서대로 한 번에 하나씩 실행하기를 원하지만이 전체 프로세스가 비동기 적으로 실행되기를 바랍니다. 대기열이 고정되어 있지 않으므로 추가 대리자가 주기적으로 추가되며 대기열의 맨 위에 도달하면 즉시 처리해야합니다.

특히 Queue을 사용할 필요가 없습니다. 원하는 동작을 설명하는 방법입니다.

무언가를 쓸 수는 있지만 무언가가 내장되어 있다면 대신 사용할 수 있습니다.

순차적으로 실행이 가능하지만 한 번에 두 개 이상의 실행을 방지하는 만족스러운 방법을 찾을 수 있으므로 간단히 살펴 보니 ThreadPool.QueueUserWorkItem입니다.

+0

어떤 부분을 정확히 비동기해야 하는가? 대기열? 또는 액션 자체가 비동기가되어야합니까? – i3arnon

+0

@ I3arnon 그것은 분명히해야합니다. 작업이 비동기 적으로 실행되어야합니다. 대기열 작업이 동 기적으로 실행되어야하며, 필수적이지는 않지만 명령이 잠재적으로 변경 될 수 있습니다. – Ashigore

+0

@ I3arnon 분명하지 않은지는 잘 모르겠습니다. 내 말은 작업 대기열에 넣기 전까지는 대기열 작업이 차단되어야한다는 것입니다. 그 후에 모든 것은 동작의 실행을 포함하여 다른 스레드에서 발생해야합니다. 하지만 한 번에 하나씩 실행해야합니다. – Ashigore

답변

4

거기를 비동기 적으로 대리자 큐를 실행합니까?

사용자 지정 작업 스케줄러로 구현하고 싶습니다. 그런 다음 대리인을 작업으로 대기열에 넣고 실행하면 예외 처리, 취소 및 async/await의 모든 이점을 얻을 수 있습니다.

대리인을 연속 주문으로 실행하는 작업 스케줄러 구현은 BlockingCollection을 사용하여 매우 간단합니다. 아래의 SerialTaskSchedulerStephen Toub's StaTaskScheduler의 단순화 된 버전입니다 :

using System; 
using System.Collections.Concurrent; 
using System.Collections.Generic; 
using System.Linq; 
using System.Threading; 
using System.Threading.Tasks; 

namespace Console_21628490 
{ 
    // Test 
    class Program 
    { 
     static async Task DoWorkAsync() 
     { 
      using (var scheduler = new SerialTaskScheduler()) 
      { 
       var tasks = Enumerable.Range(1, 10).Select(i => 
        scheduler.Run(() => 
        { 
         var sleep = 1000/i; 
         Thread.Sleep(sleep); 
         Console.WriteLine("Task #" + i + ", sleep: " + sleep); 
        }, CancellationToken.None)); 

       await Task.WhenAll(tasks); 
      } 
     } 

     static void Main(string[] args) 
     { 
      DoWorkAsync().Wait(); 
      Console.ReadLine(); 
     } 
    } 

    // SerialTaskScheduler 
    public sealed class SerialTaskScheduler : TaskScheduler, IDisposable 
    { 
     Task _schedulerTask; 
     BlockingCollection<Task> _tasks; 
     Thread _schedulerThread; 

     public SerialTaskScheduler() 
     { 
      _tasks = new BlockingCollection<Task>(); 

      _schedulerTask = Task.Run(() => 
      { 
       _schedulerThread = Thread.CurrentThread; 

       foreach (var task in _tasks.GetConsumingEnumerable()) 
        TryExecuteTask(task); 
      }); 
     } 

     protected override void QueueTask(Task task) 
     { 
      _tasks.Add(task); 
     } 

     protected override IEnumerable<Task> GetScheduledTasks() 
     { 
      return _tasks.ToArray(); 
     } 

     protected override bool TryExecuteTaskInline(
      Task task, bool taskWasPreviouslyQueued) 
     { 
      return _schedulerThread == Thread.CurrentThread && 
       TryExecuteTask(task); 
     } 

     public override int MaximumConcurrencyLevel 
     { 
      get { return 1; } 
     } 

     public void Dispose() 
     { 
      if (_schedulerTask != null) 
      { 
       _tasks.CompleteAdding(); 
       _schedulerTask.Wait(); 
       _tasks.Dispose(); 
       _tasks = null; 
       _schedulerTask = null; 
      } 
     } 

     public Task Run(Action action, CancellationToken token) 
     { 
      return Task.Factory.StartNew(action, token, TaskCreationOptions.None, this); 
     } 

     public Task Run(Func<Task> action, CancellationToken token) 
     { 
      return Task.Factory.StartNew(action, token, TaskCreationOptions.None, this).Unwrap(); 
     } 

     public Task<T> Run<T>(Func<Task<T>> action, CancellationToken token) 
     { 
      return Task.Factory.StartNew(action, token, TaskCreationOptions.None, this).Unwrap(); 
     } 
    } 
} 

출력 :

 
Task #1, sleep: 1000 
Task #2, sleep: 500 
Task #3, sleep: 333 
Task #4, sleep: 250 
Task #5, sleep: 200 
Task #6, sleep: 166 
Task #7, sleep: 142 
Task #8, sleep: 125 
Task #9, sleep: 111 
Task #10, sleep: 100 
+1

이것은 또한 매우 도움이되었고 많은 일을 했음에 틀림 없습니다. 4.5로 업그레이드하지 않고도이 솔루션을 사용할 수 있습니다. – Ashigore

+0

@Ashigore, 문제 없습니다. 대부분의 작업이 Stephen Toub에 의해 수행되었습니다. http://blogs.msdn.com/b/pfxteam/archive/2010/04/07/9990421.aspx – Noseratio

2

TPL dataflowActionBlock을 사용하고 Delegate 및 매개 변수 목록을 보유하는 클래스를 대기열에 올려 놓기 만하면됩니다. ActionBlock은 위임자를 한 번에 하나씩 만 실행합니다.

var block = new ActionBlock<Item>(_ => _.Action.DynamicInvoke(_.Paramters)); 

class Item 
{ 
    public Delegate Action { get; private set; } 
    public object[] Parameters { get; private set; } 

    public Item(Delegate action, object[] parameters) 
    { 
     Action = action; 
     Parameters = parameters; 
    } 
} 

이보다 쉽게 ​​옵션은 ActionBlockAction의 사용하는 것입니다,하지만 매개 변수를 캡처하도록 강제 : 날을 허용 할 프레임 워크에서 뭔가

var block = new ActionBlock<Action>(action => action()); 

block.Post(() => Console.WriteLine(message)); 
+0

예를 들어, 모든 대리인이 동일한 서명을 가지고 있다면, 1 문자열 매개 변수가있는 작업에 대해 새 ActionBlock >을 사용할 수 있습니까? – Ashigore

+0

@Ashigore 예. 하지만 이것이 매우 구체적인 해결책이라는 것을 기억하십시오. – i3arnon

+0

알겠습니다.이 질문에 도움을 주셔서 대단히 감사합니다. – Ashigore