2017-02-14 12 views
-1

У меня есть много запросов на отправку на сервер (API рекламы продукта Amazon), однако этот сервер не позволяет мне отправлять более 1 запроса каждую секунду. Я ищу бесплатное решение на основе возможностей C#.Как реализовать ограниченные вызовы API в многопоточной среде?

Я попытался написать код так:

public static WebResponse Request(Dictionary<string, string> parameters) { 
    var response = LaunchRequest(parameters); 
    Sleep(1000); 
    return response; 
} 

Проблема заключается в том, что несколько потоков ввода в методе «Запрос» в то же время. Если есть 4 потока, тогда будет 4 запроса в секунду.

Как мои потоки могут ждать друг друга?

EDIT: Я попытался с замком.

Так что я написал этот код:

public class RequestHandler { 
    private static readonly RequestHandler instance = new RequestHandler(); 

    private Object thisLock = new Object(); 

    private RequestHandler() { } 

    public static RequestHandler Instance { 
    get { 
     return instance; 
    } 
    } 

    public WebResponse Request(Dictionary<string, string> parameters) { 
    lock (thisLock) { 
     Log(); 
     var response = LaunchRequest(parameters); 
     Sleep(1000); 
     return response; 
    } 
    } 
} 

Так я называю мой метод, как это:

// ... 
RequestHandler requestHandler = RequestHandler.Instance; 
WebResponse response = requestHandler.Request(requestHelper, parameters); 
// ... 

Это, кажется, работает большую часть времени, но иногда, мой «LaunchRequest» метод стреляли почти в одно и то же время.

EDIT 2: Ниже приведены результаты журнала, стрелки показывают те вызовы, где есть меньше, чем 1 секунда:

Request: 09:52:50.230 - Thread 22 
Request: 09:52:48.830 - Thread 5 
Request: 09:52:47.468 - Thread 10 <--- 
Request: 09:52:47.331 - Thread 13 <--- 
Request: 09:52:45.971 - Thread 12 
Request: 09:52:44.767 - Thread 11 
Request: 09:52:43.230 - Thread 5 
Request: 09:52:30.546 - Thread 21 <--- 
Request: 09:52:30.357 - Thread 20 <--- 
Request: 09:52:29.232 - Thread 13 
Request: 09:52:27.908 - Thread 11 
Request: 09:52:26.471 - Thread 5 
Request: 09:52:25.138 - Thread 11 
Request: 09:52:23.835 - Thread 12 
+0

https://msdn.microsoft.com/en-us/library/c5kehkcz.aspx – trailmax

+0

Также это может быть более читаемым: https://www.dotnetperls.com/lock – trailmax

+0

Я редактировал свой пост с новой реализацией , используя блокировку, но все еще есть проблема. – Lawi

ответ

2

Создание очереди SQS. В вашем контроллере поместите данные в очередь. Создайте службу .net, которая будет поочередно опросать очередь и обрабатывать сообщения, гарантируя, что в любой момент времени отправляется только одно сообщение.

UPD Не знаете, почему вы отметили сообщение как AWS, если вам не нравятся службы AWS, но все же.

Вместо SQS вы можете использовать любую из доступных очередей, например. MSMQ, RabbitMQ и т. Д. Стоимость будет заключаться в необходимости устанавливать и поддерживать их самостоятельно.

Абсолютный сценарий наихудшего случая, используйте тип ConcurrentQueue от C# и выполняйте фоновый поток, который будет выходить из этой очереди. Это гарантирует, что за один раз обрабатывается только одно сообщение. Однако это решение не является устойчивым, поскольку утилизация пула приложений будет уничтожать ваши данные.

+0

Я ищу бесплатное решение. Я действительно думал, что в C# это возможно. – Lawi

+2

@Lawi Это правильный ответ. Вы не упомянули нигде в своем вопросе о том, что решение должно быть бесплатным. Далее вы отметили вопрос с помощью AWS, и именно так вы решаете это с помощью AWS.Вы посмотрели на SQS, чтобы убедиться, что ваше использование будет проходить через свободный уровень? –

+0

Ну ладно, мне нужно было уточнить, что я хочу бесплатное решение. Я редактировал сообщение, чтобы добавить эту спецификацию. – Lawi

2

Сохраните последние данные вызова в сеансе или локальную переменную. Затем вы можете вызвать сон, если есть вызов.

public static WebResponse Request(Dictionary<string, string> parameters) 
{ 
    lock (thisLock) 
    { 
     string lastCalled = Session["LastCalledTime"] as string; 
     if (!string.IsNullOrEmpty(lastCalled) && DateTime.Parse(lastCalled) >= DateTime.Now.AddSeconds(-1)) 
     { 
      Sleep(1000); 
     } 
     var response = LaunchRequest(parameters); 
     Session["LastCalledTime"] = DateTime.Now.ToString("O"); 
    } 

    return response; 
} 

Добавлен замок, чтобы сделать его потоковым, как выделено trailmax.

+0

Это еще небезопасное сообщение – trailmax

+0

@trailmax: Я думаю, он исправлен. – Stefan

+1

Так зачем же исключать 'LaunchRequest' из замка? также в какой смысл использовать сеанс, когда мы знаем, что в любом случае нам нужно подождать секунду после запроса - просто сделайте 'LaunchRequest(); Thread.Sleep (1000); ' – trailmax