2014-01-16 3 views
8

의 난이 간단한 조각이 있다고 가정하자async/await을 직렬화하는 방법은 무엇입니까?

async void button_Click(object sender, RoutedEventArgs e) 
{ 
    await Task.Factory.StartNew(() => 
    { 
     Console.WriteLine("start"); 
     Thread.Sleep(5000); 
     Console.WriteLine("end"); 
    }); 
} 

은 물론, 매번 나는 이전 작업이 여전히 실행하면 새 작업도 시작 버튼 있음을 누릅니다. 이전 작업이 모두 끝날 때까지 새로운 작업을 연기하려면 어떻게합니까?

좀 더 자세한 사항 : 위의 예에서

은, 각각의 새로운 작업은 이전 작업과 동일합니다. 그러나 원래 컨텍스트에서는 작업 순서가 중요합니다. 매개 변수가 변경 될 수 있습니다 (DateTime.Now.Ticks을 사용하여 "시뮬레이션"할 수 있음). 작업은 "등록"된 순서대로 실행해야합니다. 구체적으로 말하자면, 내 프로그램은 시리얼 장치와 통신 할 것이다. 전에 BlockingCollection을 사용하는 백그라운드 스레드로이 작업을 수행했습니다. 그러나 이번에는 엄격한 요청/응답 프로토콜이 있으며 가능한 경우 async/await를 사용하고 싶습니다.

가능한 해결 방법 : 나는 작업을 생성하고 목록에 저장 상상할 수

. 그러나 요구 사항과 관련된 작업을 어떻게 수행 할 수 있습니까? 또는 이전에 사용한 스레드 기반 솔루션으로 돌아 가야합니까?

+3

여전히 'ConcurrentQueue' 또는'BlockingCollection'을 사용할 수 있습니다. 생산자 - 소비자 시나리오이므로 후자가 가장 적합해야합니다. –

+0

예. 그러나 내가보기에, 나는 대기열에서 기다리고 그것을 반복 할 필요가있다. 작업을 생성하기 위해'작업'또는 데이터를 저장하겠습니까? 'Task' 내부에서 큐를 루프 시키거나 루프가'Task'를 시작할 것인가?그럼에도 불구하고, 이것은별로 문제가되지 않지만, 나는 그 요청/응답 패턴에 더 적합한 또 다른 방법이 있기를 희망했다. – JeffRSon

답변

6

SemaphoreSlim을 비동기 적으로 기다렸다가 작업이 완료되면 놓을 수 있습니다. 세마포어 초기 값을 1으로 설정하는 것을 잊지 마십시오.

private static SemaphoreSlim semaphore = new SemaphoreSlim(1); 

private async static void DoSomethingAsync() 
{ 
    await semaphore.WaitAsync(); 
    try 
    { 
     await Task.Factory.StartNew(() => 
     { 
      Console.WriteLine("start"); 
      Thread.Sleep(5000); 
      Console.WriteLine("end"); 
     }); 
    } 
    finally 
    { 
     semaphore.Release(); 
    } 
} 

private static void Main(string[] args) 
{ 
    DoSomethingAsync(); 
    DoSomethingAsync(); 
    Console.Read(); 
} 
+0

Try-finally를 잊어 버리고 finally 절의 세마 폰을 릴리스하십시오! 그렇지 않으면, 당신의 작업에서 예외가 던져지면 사용자에게 상당히 좌절 할 수 있습니다. – Olivier

+0

@Olivier Thanks, 아주 분명히 업데이트되었습니다! –

+0

이것은 가끔씩 동시 액세스를 대기열에 넣어야하는 이유입니다. – JeffRSon

8

동기화를 위해 SemaphoreSlim을 사용하는 것이 좋습니다. 그러나 avoid Task.Factory.StartNew (블로그에서 설명 함) 및 definitely avoid async void (MSDN 기사에서 설명 함)을 원합니다.

private SemaphoreSlim _mutex = new SemaphoreSlim(1); 
async void button_Click(object sender, RoutedEventArgs e) 
{ 
    await Task.Run(async() => 
    { 
    await _mutex.WaitAsync(); 
    try 
    { 
     Console.WriteLine("start"); 
     Thread.Sleep(5000); 
     Console.WriteLine("end"); 
    } 
    finally 
    { 
     _mutex.Release(); 
    } 
    }); 
} 
+0

기사에 링크 해 주셔서 감사합니다! 아주 좋아! BTW, 내가 당신의 예제를 시도했을 때 그것을 컴파일 할 수 있도록'async'로 람다 식을 표시해야했다. 이 올바른지? – JeffRSon

+0

@StephenCleary, [this] (http://stackoverflow.com/a/21178958/1768303)와 같은 이전 작업을 '대기'할 수있는 이유가 있습니까? – Noseratio

+2

@Noseratio : 그 방법도 효과적입니다. IMO는'SemaphoreSlim' 코드가 명확한 의도를 가지고 있습니다. 'await' - 이전 태스크 코드에서 이전 태스크를 복사하는 것이 중요하다는 것은 분명하지 않다. –

2

뭔가 누락되었을 수 있지만 OP의 시나리오에는 SemaphoreSlim이 필요하지 않습니다. 나는 그것을 다음과 같이 할 것이다. 기본적으로, 코드 단지 await (선명도에 대한 예외 처리를) 계속하지 전에 작업의 이전 보류 예 :

// the current pending task (initially a completed stub) 
Task _pendingTask = Task.FromResult<bool>(true); 

async void button_Click(object sender, RoutedEventArgs e) 
{ 
    var previousTask = _pendingTask; 

    _pendingTask = Task.Run(async() => 
    { 
     await previousTask; 

     Console.WriteLine("start"); 
     Thread.Sleep(5000); 
     Console.WriteLine("end"); 
    }); 

    // the following "await" is optional, 
    // you only need it if you have other things to do 
    // inside "button_Click" when "_pendingTask" is completed 
    await _pendingTask; 
} 

[업데이트] 코멘트를 해결하기 위해, 여기 스레드 안전 버전은, 때 button_Click는 할 수 있어요 동시에 호출 :

Task _pendingTask = Task.FromResult<bool>(true); 
object _pendingTaskLock = new Object(); 

async void button_Click(object sender, RoutedEventArgs e) 
{ 
    Task thisTask; 

    lock (_pendingTaskLock) 
    { 
     var previousTask = _pendingTask; 

     // note the "Task.Run" lambda doesn't stay in the lock 
     thisTask = Task.Run(async() => 
     { 
      await previousTask; 

      Console.WriteLine("start"); 
      Thread.Sleep(5000); 
      Console.WriteLine("end"); 
     }); 

     _pendingTask = thisTask; 
    } 

    await thisTask; 
} 
+0

둘 이상의 "보류 중"작업이있을 수 있습니다. – JeffRSon

+1

@JeffRSon, 둘 이상으로 작동한다. 모두 'button_Click'(또는 다른 래퍼 메소드)로 시작하는 한 오래 걸린다. 이것은 for (var i = 0; i <10; i ++) button_Click (null, null);에 대한 [초기 버전] (http://stackoverflow.com/revisions/21178958/1) 이는'_pendingTask' 멤버를 로컬'previousTask'에 캡처하여 'Task.Run' 람다 내부에서 사용하기 때문에 작동합니다. – Noseratio

+0

아 - 그래. 따라서 "동기화"의 수단으로 GUI 스레드에 의존하게됩니다. – JeffRSon

2

무엇 (기본값)과 1의 최대 병렬 처리 수준은 우려를 잠금/스레드 안전의에 대해 걱정할 필요가 없습니다이 방법을 Dataflow.ActionBlock<T> 시도에 대해. 또한 설치가 ActionBlock는이 Task 또는 Func<Task>, 단순히 실행 /이 입력을 기다리고받을 수

... 
var _block = new ActionBlock<bool>(async b => 
       { 
        Console.WriteLine("start"); 
        await Task.Delay(5000); 
        Console.WriteLine("end"); 
       }); 
... 


async void button_Click(object sender, RoutedEventArgs e) 
{ 
    await _block.SendAsync(true); 
} 

:

이 같은 것을 볼 수 있었다. 여러 작업을 대기열에두고 다른 소스에서 기다릴 수 있습니다.

관련 문제