2011-09-23 2 views
2

제 3 자 웹 서비스를 사용하고 기능을 크게 확장하는 WebService를 작성하고 있습니다. 예를 들어, 워크 플로의 한 부분에서 하나의 API 호출 결과를 반복해야하고 각 결과에 대해 실제로 사용할 수있는 결과를 반환하기 위해 다른 API 호출을 만들어야합니다. 현재 약 7,500 줄의 XML과 3 ~ 4 분의 로딩 시간이 있습니다 (이로드 시간은 Visual Studio에서 디버그 모드로 WebService를 실행하는 것을 기반으로합니다. 하이 엔드 Windows 서버에서 실행될 때 약간 더 부드럽게 작동 할 것으로 기대하십시오). 내가 뭘하고 싶은지 각 비서 호출을위한 새로운 Asyncronous 스레드를 생성하는 방법을 찾을 수 있습니다 (그래서 각 반복은 이전 반복이 끝날 때까지 기다릴 필요가 없습니다), 그러나 나는 이것을 어떻게 아직도하는지 잘 모르겠습니다. 동일한 함수 호출에서 XML 출력을 리턴 할 수 있어야합니다. 어떤 아이디어?비동기 호출로 웹 서비스의 속도를 높이려는 것이지만 확실하지는 않습니다.

:: EDIT :: - 다음은 XML을 생성하는 코드입니다. 모든 함수 호출은 타사 API에 대한 API 호출에 대한 래퍼 일뿐입니다.

public List<AvailabilityList> getResortsForDate(String month, int year) { 
     List<RegionList> regions = this.getRegionLists(); 
     List<AvailabilityList> availability = new List<AvailabilityList>(); 
     foreach(RegionList parent in regions) 
     { 
      foreach(Region child in parent.Regions) 
      { 
       if (!String.Equals(child.ID, "?")) 
       { 
        int countryID = Int32.Parse(parent.CountryID); 
        AvailabilityList current = this.getExchangeAvailability(countryID, month, year, child.ID); 
        if (current.ListCount != 0) 
        { 
         availability.Add(current); 
        } 
       } 
      } 
     } 
     return availability; 
    } 

:: 편집 # 2 : 해결책! 이것은 제가 사용했던 솔루션입니다. 이것은 제가 선택한 대답에 대한 사소한 조정입니다. 감사! 이전 코드 (5 분 1 초)를 계산 한 후,이 코드는 1 분 6 초에 큰 발전을 보였습니다. 30 초의 시간이 내가 최적화 할 다른 방법에 속합니다.

public List<AvailabilityList> _asyncGetResortsForDate(String month, int year) { 
     List<RegionList> regions = this.getRegionLists(); 
     List<AvailabilityList> availability = new List<AvailabilityList>(); 

     List<WaitHandle> handles = new List<WaitHandle>(); 
     List<AvailabilityList> _asyncResults = new List<AvailabilityList>(); 

     regions.ForEach(parent => { 
      parent.Regions.ForEach(child => { 
        if (!String.Equals(child.ID, "?")) { 
         int countryID = Int32.Parse(parent.CountryID); 

         Func<AvailabilityList> _getList =() => this.getExchangeAvailability(countryID, month, year, child.ID); 

         IAsyncResult res = _getList.BeginInvoke(new AsyncCallback(
          x => { 
           AvailabilityList result = (x.AsyncState as Func<AvailabilityList>).EndInvoke(x); 
           if (result.ListCount > 0) 
           { 
            _asyncResults.Add(result); 
           } 

          }), _getList); 
         while (handles.Count >= 60) 
         { 
          int item = WaitHandle.WaitAny(handles.ToArray()); 
          handles.RemoveAt(item); 
         } 
         handles.Add(res.AsyncWaitHandle); 
        } 
       }); 
     }); 

     WaitHandle.WaitAll(handles.ToArray()); 

     return _asyncResults; 
    } 
+0

함께 작업하는 아이디어를 제공하는 코드를 게시해야합니다. 또한 반복되는 결과의 수와 원격 서비스에서 얼마나 많은 데이터를 다운로드하고 있습니까? 비동기식으로이 작업을 수행하면 성능이 전반적으로 향상된다는 보장이 반드시 필요한 것은 아니며 다른 요소가 많습니다. 동기 호출을 사용하는 것이 병목 일 수도 있고 아닐 수도 있습니다. –

+1

'_asyncResults.Add (result)'는 여러 스레드가 동시에 그것을 실행하면 문제가 발생할 것입니다. 잠금 장치 또는 다른 유형의 동기화로 보호해야합니다. –

답변

0

여기에 모든 스레드가 최종 목록을 반환하기 전에 완료 될 때까지 대기합니다, 때마다 당신이 getExchangeAvailability을 (전화 비동기 작업을 수행하는 하나의 방법)은 별도의 스레드에서 그렇게이다. 마음에

public List<AvailabilityList> _asyncGetResortsForDate(String month, int year) 
    { 
     List<RegionList> regions = this.getRegionLists(); 
     List<AvailabilityList> availability = new List<AvailabilityList>(); 

     List<WaitHandle> handles = new List<WaitHandle>(); 
     List<AvailabilityList> _asyncResults = new List<AvailabilityList>(); 

     regions.ForEach(parent => 
     { 
      parent.Regions.ForEach(child => 
      { 
       if (!String.Equals(child.ID, "?")) 
       { 
        int countryID = Int32.Parse(parent.CountryID); 

        Func<AvailabilityList> _getList =() => 
         this.getExchangeAvailability(countryID, month, year, child.ID); 

        IAsyncResult res = _getList.BeginInvoke(new AsyncCallback(
         x => 
         { 
          AvailabilityList result = 
          (x.AsyncState as Func<AvailabilityList>).EndInvoke(x); 

          _asyncResults.Add(result); 

         }), _getList); 

        handles.Add(res.AsyncWaitHandle); 
       } 
      }); 
     }); 

     WaitHandle.WaitAll(handles.ToArray()); 

     return _asyncResults; 
    } 

유지하지만 그 반복의 수는 동시 스레드의 기본 최대 수 (BeginInvoke를 사용하여가()), 64 비동기 그 시점까지 후 아무것도 처리되지 않습니다되고, 64을 초과하는 경우 이미 실행중인 64 개의 스레드 중 하나가 해제됩니다. 또한 스레드간에 컨텍스트를 전환하는 데 약간의 오버 헤드가있을 수도 있고 그렇지 않을 수도 있습니다. 한 가지 확인해야 할 것은 각 API 호출이 실제로 얼마나 가치가 있는지 확인하는 데 걸리는 시간입니다.

편집 - 64 스레드 제한 오류에 관한, 나는 두 가지를 제안

1)해야 그룹 각 '부모'가 아니라 모든보다는, 자신의 스레드에서 실행되도록 비동기 호출 한 아이. 즉, 같은 것을 스레드의 #을 줄여야합니다 :

public List<AvailabilityList> _getAllChildren(RegionList parent, string month, int year) 
    { 
     List<AvailabilityList> list = new List<AvailabilityList>(); 

     parent.Regions.ForEach(child => 
     { 
      if (!String.Equals(child.ID, "?")) 
      { 
       int countryID = Int32.Parse(parent.CountryID); 

       AvailabilityList result = this.getExchangeAvailability(countryID, month, year, child.ID); 

       list.Add(result); 
      } 
     }); 

     return list; 
    } 

    public List<AvailabilityList> _asyncGetResortsForDate(String month, int year) 
    { 
     List<RegionList> regions = this.getRegionLists(); 
     List<AvailabilityList> availability = new List<AvailabilityList>(); 
     List<WaitHandle> handles = new List<WaitHandle>(); 

     List<AvailabilityList> _asyncResults = new List<AvailabilityList>(); 

     regions.ForEach(parent => 
     { 
      Func<List<AvailabilityList>> allChildren =() => _getAllChildren(parent, month, year); 

      IAsyncResult res = allChildren.BeginInvoke(new AsyncCallback(
         x => 
         { 
          List<AvailabilityList> result = 
          (x.AsyncState as Func<List<AvailabilityList>>).EndInvoke(x); 

          _asyncResults.AddRange(result); 

         }), allChildren); 

      handles.Add(res.AsyncWaitHandle); 
     }); 

     WaitHandle.WaitAll(handles.ToArray()); 

     return _asyncResults; 
    } 

2) 당신은 64 개 스레드를 초과하는 위험이 있는지 확인하기 위해 WaitHandles의 목록에 추가하기 전에 검사를 추가 할 필요가 있습니다

 var asyncHandle = res.AsyncWaitHandle; 

    if (handles.Count >= 64) 
      asyncHandle.WaitOne(); // wait for this one now 
    else if (handles.Count < 64) 
      handles.Add(asyncHandle); 
+0

WaitHandle.WaitAll (handles.ToArray())를 사용하여 예외가 발생하는 것으로 보입니다. 라인, 64 개 이상의 스레드가 있다는 사실에 관한. 이 오류를 처리하는 가장 좋은 방법은 무엇이라고 생각하십니까? –

+0

@ 브레드 든, 내 편집을 참조하십시오. –

+0

나는 실제로 당신의 대답에 기초한 해결책을 찾았습니다! 기본 질문을 대답으로 업데이트하고 대답을 선택하겠습니다. 감사! –

1

이와 같이 대기 핸들의 배열을 골라내는 것은 코드에서 너무 복잡해 보이는 신호입니다. 작업 병렬 라이브러리를 사용하면 훨씬 더 명확한 작업을 수행 할 수 있습니다. 예를 들어

:

public List<AvailabilityList> _asyncGetResortsForDate(String month, int year) 
{ 
    List<RegionList> regions = this.getRegionLists(); 
    List<AvailabilityList> availability = new List<AvailabilityList>(); 

    List<Task> tasks = new List<Task>(); 
    List<AvailabilityList> _asyncResults = new List<AvailabilityList>(); 

    regions.ForEach(parent => 
    { 
     parent.Regions.ForEach(child => 
     { 
      if (!String.Equals(child.ID, "?")) 
      { 
       int countryID = Int32.Parse(parent.CountryID); 
       var childId = child.ID; 

       Task t = Task.Factory.StartNew((s) => 
        { 
         var rslt = getExchangeAvailability(countryId, month, year, childId); 
         lock (_asyncResults) 
         { 
          _asyncResults.Add(rslt); 
         } 
         }); 
       tasks.Add(t); 
      } 
     }); 
    }); 

    Task.WaitAll(tasks); 

    return _asyncResults; 
} 

(나는 그것을 컴파일 시도하지 않은,하지만 당신은 생각의 요점을 얻는다.)

64 대기 핸들 제한에 대한 TPL 걱정을 보자.

또한 코드에 버그가 발생하기를 기다리고 있습니다. 여러 작업이 결과를 _asyncResults 목록에 추가하려고 시도 할 수 있으므로 잠금으로 보호해야합니다. List<T>.Add은 스레드로부터 안전하지 않습니다. 두 스레드가 동시에 액세스하려고하면 데이터가 손상되거나 예외가 발생합니다.

위의 내용이 더 빠를 수도 있습니다. 비동기 호출을 여러 번 시작하면 어떻게 될지 잘 모르겠습니다. 스레드 풀이 스레드 풀의 최대 개수를 만들고 실행을 모두 시작하는 것 같습니다. 25 개 이상의 실행중인 스레드와 함께 제공된 컨텍스트 스위치 등으로 끝날 수 있습니다. 반면 TPL은 스레드 사용에 대해 훨씬 더 똑똑합니다. 동시 스레드가 적어지기 때문에 대량의 컨텍스트 전환을 피할 수 있습니다.

Task<List<AvailabilityList>>을 사용하면 잠금을 피할 수 있습니다. 당신의 Task.WaitAll(tasks) 후 다음

Task<List<AvailabilityList>> t = Task<List<AvailabilityList>>.Factory.StartNew((s) => 
    { 
     return getExchangeAvailability(countryId, month, year, childId); 
    } 

그리고, : 코드는 다음과 같이됩니다

foreach (var t in tasks) 
{ 
    _asyncResults.Add(t.Result); 
} 

사실, 당신이 결과를 사용할 수있을 때까지 Task<T>.Result 블록 때문에, Task.WaitAll(tasks) 제거 할 수 있습니다.