2012-01-05 2 views
4

Command 패턴을 사용하여 비동기 작업을 처리하는 가장 좋은 방법에 대한 의견을 듣고 싶습니다.C#의 명령 패턴 및 비동기 작업 처리

public class MyCommand 
{ 
    // Sets up receiver and does whatever stuff 

    public void Execute() 
    { 
     _myReceiver.DoSomething(); 
    } 
} 

이 문제는 다음과 같습니다 : MyCommand이 MyReceiver.DoSomething은() 코드의 비동기 부분이 있는지 여부를 알 수 없습니다 우리는 다음과 같은 예를 말해봐. 실행 후 MyCommand를 실행 취소 스택에 넣으려고하면 해당 수신자 작업이 완전히 실행되었음을 보장 할 수 없어 MyCommand가 실행 취소가 가능한 상태에 도달했는지 여부를 알 수 없습니다.

나는 개인적으로 다음과 같은 솔루션에 대한 생각 :

  1. 가 MyReceiver에서 이벤트를 포함
  2. 이 명령에서 "BeginExecute"와 "EndExecute"를 포함 명령의 국가 통제의 일종을 구현하고 명령 구독하기

    012 : 그들에게 일을 마무리하려면, MyCommand로 전환 할

(즉, 나에게 냄새 나는 것 같다)

이제 명령 수신자가 어떤 작업을 수행했는지 확인하고 실행 취소 스택에 푸시 할 준비가되었음을 알 수 있습니다. 그러나 이벤트 스팸의 경우 비동기 작업이 포함 된 모든 단일 수신기 클래스가 실제로 버그가 발생합니다.

인터넷에서이 주제에 대해 많이 알지 못했지만 다른 접근법을 듣고 싶습니다.

OBS : 모든 비동기 관련 코드를 관리하는 명령을 옵션이 아닙니다. :).

+0

어떻게하면 _myReceiver.DoSomething(); 일부 비동기 속성이 있습니다. 완료되었거나 비동기 동작을 제어 할 수있는 무언가를 반환합니다. 어쨌든 _myReceiver.DoSomething(); 당신의 명령이 아닙니다. – Euphoric

+0

실행 취소 부분이 어떻게 보이는지 - 비동기 실행을 명령하거나 ... ...? –

+0

@Euphoric DoSomething 메서드는 .BeginInvoke something ... –

답변

1

뭔가?

public interface ICommand 
{ 
    void Execute(); 
    event EventHandler Finished; 
} 

public class MyCommand : ICommand 
{ 
    public MyCommand(MyReceiver receiver) 
    { 
     _myReceiver = receiver; 
     _myReceiver.DoSomethingFinished +=() => Finished(); // dont forget null check here. 
    } 

    public void Execute() 
    {  
     _myReceiver.DoSomething(); 
    } 

    public event EventHandler Finished; 
} 

이 방법은이 명령의 사용자는 명령이 비동기 동작을 완료하고 acordingly 역할을 할 수있을 때 알 수 있도록 이벤트를 완료하기 위해 등록 할 수 있습니다.

또는 이벤트를 사용하지 않겠다고한다면 콜백은 어떻게됩니까?

public class MyCommand : ICommand 
{ 
    public MyCommand(MyReceiver receiver) 
    { 
     _myReceiver = receiver; 
    } 

    public void Execute() 
    {  
     _myReceiver.DoSomething(() => Finished()); // dont forget null check here. 
    } 

    public event EventHandler Finished; 
} 

어느 쪽이든 MyReciever가 호출자에게 완료되었음을 알리는 방법 만 있으면됩니다. 그것을 무시할 방법이 없습니다.

+0

답장을 보내 주셔서 감사합니다. 문제는 명령의 완료 신호와 관련이 없습니다. 사실 단순화를 위해 예제에서 그 부분을 생략했습니다. Receiver에서 이벤트를 사용하는 것이 불필요한 접근법을보고 싶습니다. 귀하의 예를 들어, 나는 여전히 처리 할 수있는 각각의 비동기 작업에 대한 이벤트가있는 수신기를 스팸해야합니다. – ferspanghero

+0

@ferspan : MyReciever와 다른 모든 것들에 공통적 인 추상화가 없다면, 같은 방식으로 사용될 것입니다. 그렇다면 가능한 해결책이 없습니다. 이것은 현재의 프로그래밍에서 상당히 큰 문제이며 완전히 새로운 C# 버전이 비동기, 대기 및 작업으로이 문제에 커밋됩니다. – Euphoric

+0

고마워요! 귀하의 접근 방식은 Tigram 's와 유사하게 끝나게되었습니다. 따라서, 나는 같은 질문을한다 : _myReceiver.DoSomething (Action action)이 비동기 호출을 많이한다는 점을 감안할 때, 모든 비동기 호출이 완료된 후에 콜백이 어떻게 실행될 수 있을까? – ferspanghero

1

먼저 메서드 Async의 이름을 추가하여 메서드를 비동기 방식으로 실행하는 Command 클래스 소비자에게 명시 적으로 신호를 보냅니다.

두 번째로 매개 변수처럼 Action<T>을 추가하면 비동기 호출 완료 메서드라고 부릅니다. 따라서 비동기 호출이 종료되면이 메소드 호출자에게 통지 할 수 있습니다. 이 같은

편집

obj.DoSomethingAsync(... params, Action<T> onComplete)

+0

정확히 조치를 추가할까요? BeginExecute이면 _myReceiver.DoSomething()이 같은 방식으로 반환되어 비동기 상태에 대해 알지 못하게되므로 아무 쓸모가 없습니다. 그것은 _myReveiver.DoSomething()에 있다면, 나는 여전히 청소기를 찾을 수있는 이벤트 솔루션을 고수 할 것입니다. – ferspanghero

+0

@ferspan : 그 클래스에서 20 개의 비동기 메소드를 가지고 있고 그 중 몇 개를 다음에 실행 해보면 어떨까요? 이걸 어떻게 다룰 거니? 각 메소드는 * 자신의 이벤트를 정의해야합니까? 아니면 이벤트 자체가 메소드 정의를 가져올 것인가? ** Imo **,'Action '패턴이 더 깨끗함 – Tigran

+0

@ferspan : 내 편집 된 게시물보기. – Tigran

1

호출 코드의 동작을 수정하지 않고 제어가 Execute 메서드로 반환되기 전에 모든 처리가 완료되어야한다는 요구 사항을 적용하려는 경우 동작을 실행하는 방식을 수정할 수 있습니다.

먼저 모든 비동기 호출을 초기화하고 현재 호출에서 반환 할 호출을 차단 (대기)하십시오. 당신이 알고있는 쓰레드에 있거나 임의의 쓰레드에서 리턴 될 것이라는 점에서 비동기 호출의 특성이 무엇인지는 잘 모르겠다. 그러나 어떤 종류의 쓰레드를 생각 해낼 수 있어야한다. 귀하의 문제에 대한 동기화.

Semaphore을 사용하여 현재의 스레드 (비동기 메소드 호출 후)를 차단하고 모든 비동기 메소드가 응답을 반환하면 세마포어를 해제하십시오. 이렇게하면 비동기 호출을 "다시 동기화"하는 효과가 있습니다.

다른 동기화 방법을 사용할 수 있지만 세마포는 이해하기 쉽습니다.

2

나는 하나의 클래스에서 너무 많은 일을 진행하고 있다고 생각합니다. 나는 이런 식으로 무너 뜨리는 것 :

// An immutable command, to be handled in-process. 
// ICommand is a marker interface with no members. 
public class DoSomething : ICommand 
{ 
    public readonly Id; 

    public DoSomething(Guid id) 
    { 
     Id = id; 
    } 
} 

// To be handled out-of-process. 
[AsynchronousCommand] 
public class DoSomethingThatTakesAReallyLongTime : ICommand 
{ 
    public readonly Id; 

    public DoSomethingThatTakesAReallyLongTime(Guid id) 
    { 
     Id = id; 
    } 
} 

// This guy could take any number of dependencies: ISomethingRepository, DbContext, etc. 
// Doesn't matter, but it's probably gonna have dependencies. 
public class DoSomethingHandler : IHandler<DoSomething> 
{ 
    public void Handle(DoSomething command) // IHandler<T>'s only member 
    { 
     // CRUD or call call a domain method 
    } 
} 

public class CommandService : ICommandService 
{ 
    public void Execute(params ICommand[] commands) // ICommandService's only member 
    { 
     foreach(var command in commands) 
     { 
      var handler = GetHandler(command); // Could use your IOC container. 

      if (HasAsyncAttribute()) 
       new Action(() => handler.Handle(command)).BeginInvoke(null, null); 
      else 
       handler.Handle(command); 
     } 
    } 
} 

// Something that might consume these 
public class SomethingController 
{ 
    private readonly ICommandService _commandService; 

    public SomethingController(ICommandService commandService) 
    { 
     _commandService = commandService; 
    } 

    [HttpPost] 
    public void DoSomething(Guid id) 
    { 
     _commandService.Execute(new DoSomething(id)); 
    } 

    [HttpPost] 
    public void DoSomethingThatTakesAReallyLongTime(Guid id) 
    { 
     _commandService.Execute(new DoSomethingThatTakesAReallyLongTime(id)); 
    } 
} 

여기에 가장 큰 장점은 명시 적으로 핸들러와 함께 갈 모든 종속성을 따라 드래그없이 클라이언트에 명령을 배포 할 수 있다는 것입니다. 핸들러는 클라이언트에게 알려서는 안됩니다. 모든 클라이언트가 알아야 할 것은 명령을 보낸 것이고 모든 명령은 성공했다고 가정해야합니다.

+0

아주 좋습니다. 고마워요!승리를위한 장식가! – trailmax