Nie wiem jak fachowo opisać mój problem, ale mam nadzieję, że zrozumiecie o co chodzi.
Mam metodę, której zadanie to wysłanie dyspozycji zlecenia do biura maklerskiego. W odpowiedzi dostaje numer dyspozycji tego zlecenia. Problem w tym, że w jednym czasie program może wysłać np. 5 dyspozycji, a czas odpowiedzi z biura to 2 sekundy, więc ostatnia dyspozycja wykona się po 10 sekundach (wszystkie dyspozycje uruchamiane będą szeregowo). Jak najprościej zapisać taką metodę, aby robiła swoje, bez blokowania programu uruchamiającego? No i pozostaje problem magazynowania odpowiedzi. Ja wymyśliłem to tak, że wysyłając dyspozycję, nadaję tej dyspozycji własny numer, któremu po odpowiedzi przypisuję zwrócony przez biuro iddyspozycji. Poproszę o podpowiedzi.
@Spine: Ok. A co się dzieje jak tą samą metodę wywołam jednocześnie asynchronicznie, a ta metoda używa statycznej klasy i metody z HttpWebRequest? Czy jednoczesne uruchomienie takiej statycznej metody jest prawidłowe, czy też powinienem użyć obiektu?
Próbowałeś już użyć async, await w swoim kodzie?
Coś nie działa?
@Spine: Jeszcze nie, ale zastanawiam się czy używanie takich statycznych metod jest prawidłowe jeśli chodzi o połączenia HTTP.
@duzers: https://docs.microsoft.com/en-us/dotnet/api/system.net.httpwebrequest?view=net-6.0
We don't recommend that you use HttpWebRequest for new development. Instead, use the System.Net.Http.HttpClient class.
Widzisz, problem rozwiązany, użyj nowego sposobu :]
https://docs.microsoft.com/en-us/dotnet/api/system.net.http.httpclient?view=net-6.0
Spine napisał(a):
Próbowałeś już użyć async, await w swoim kodzie?
Coś nie działa?
Raczej tego nie mogę zrobić ponieważ metoda, która uruchamia moją metodę nie może być async.
Bez async/await będzie ciężko jeśli rozumiem dobrze co potrzebujesz. Czy wynik musi być zwracany synchronicznie? Co jest punktem wejścia do pojedynczego procesu wysyłania dyspozycji? Jakieś UI?
@Aventus: Odpowiedź nie musi być synchronicznie. Najważniejsze, żeby poszła dyspozycja jak najszybciej, bo resztę jakoś przełknę. Punktem wyjścia jest plugin dll, a on kieruje się swoimi standardami. Jak dla mnie to schemat powinien wyglądać tak:
....
pluginThread = new Thread(new ThreadStart(PluginExecutionLoop))
....
///PluginExecutionLoop
if (backfillQueue.Count > 0 && Interlocked.Read(ref concurrentDownloads) < 5)
{
// increment no of downloads
Interlocked.Increment(ref concurrentDownloads);
// dequeue the ticker and get the tickerdata for it
tickerData = backfillQueue.Dequeue();
// make an idle threadpool thread execute the download in the background
ThreadPool.QueueUserWorkItem(MetodaDyspozycja, tickerData);
}
////
private void MetodaDyspozycja(object ticker)
{
}
Coś w ten deseń, na podstawie jakiegoś innego projektu, który jakoś tam zakumałem i pod siebie przerobiłem.
Jeśli nie musi być synchronicznie to ja bym wysłał to do jakiejś kolejki, np. RabbitMQ. Miej oddzielny proces który będzie nasłuchiwał na tej kolejce i obsługiwał wszystko asynchronicznie. Wtedy w ogóle nie będzie blokował swojego głównego procesu.
Ewentualnie jeśli obrabianie tego w osobnym procesie nie wchodzi w grę, to czemu nie oddelegować tego na osobny wątek? Lub nawet odpalić Task
którego nie będzie awaitował. Tylko wtedy wadą jest to że stracisz informację o błędach jeśli odpowiednio przed tym się nie zabezpieczysz, a jeśli w trakcie przetwarzania proces zostanie zamknięty to również utracisz te informacje które aktualnie przetwarzasz. Dla tego jakiś message broker i kolejka wydaje się bezpieczniejszą opcją.
Możesz to zrobić na wątkach, na Taskach, na ThreadPool i async/await. A nawet za pomocą TPL, Może coś w ten deseń
async Task SendRequests(int requestCount)
{
var tasks = new List<Task>();
for(int i = 0; i < requestCount; i++)
tasks[i] = Task.Run(() => SendRequest()); //zauważ, że nie użwam tyutaj async/await
var waitTask = Task.WhenAll(tasks);
try
{
await waitTask;
}
catch(Exception ex)
{
}
}
Albo z użyciem Parallel.ForEach.
Tylko jeśli Twoja metoda SendRequest jest statyczna albo nie jest po prostu thread safe, prawdopodobnie będziesz musiał zastosować jakieś locki. Ale w taki sposób jak przedstawiłem, aplikacja będzie cały czas działać. A możesz też trochę to rozbudować i wyposażyć w jakiś pasek postępu.
@Juhas: W tej chwili mam coś takiego:
public Form1()
{
InitializeComponent();
backfillQueue = new Queue<int>();
pluginAutoResetEvent = new AutoResetEvent(false);
pluginThread = new Thread(new ThreadStart(PluginExecutionLoop))
{
Name = "processing thread",
IsBackground = true
};
pluginThread.Start();
}
private void PluginExecutionLoop()
{
while (pluginIsRunning)
{
if (ProcessAllQueues())
{
pluginAutoResetEvent.WaitOne(10);
}
else
{
pluginAutoResetEvent.WaitOne(10);
}
}
}
private bool ProcessAllQueues()
{
try
{
ProcessQuotationUpdateQueue2();
return true;
}
catch (Exception exception)
{
Debug.WriteLine("Program error. ProcessQueues exception: " + exception);
return true;
}
}
private void ProcessQuotationUpdateQueue2()
{
// if there are tickers enqueued and there are less then 8 parallel downloads already running
if (backfillQueue.Count > 0 && Interlocked.Read(ref concurrentDownloads) < 8)
{
// increment no of requests
Interlocked.Increment(ref concurrentDownloads);
// dequeue the ticker and get the tickerdata for it
tickerData = backfillQueue.Dequeue();
ThreadPool.QueueUserWorkItem(RefreshTicker, tickerData);
}
}
private void RefreshTicker(object id)
{
Debug.WriteLine("\n" + "Ticker: " + id.ToString());
string request= WyslijDyspozycje();
// decrement no of downloads
Interlocked.Decrement(ref concurrentDownloads);
}
int licznik = 1;
private void button10_Click(object sender, EventArgs e)
{
//wyślij jednocześnie 6 dyspozycji
do
{
backfillQueue.Enqueue(licznik++);
} while (licznik < 6);
}
Twój kod jest świetny i robi to samo co mój. Dziękuję!
Rozumiem, że poprzez tworzenia obiektu dla metody zawierającej HttpWebRequest pozbyłbym się problemów z jakimiś zgrzytami?
Pętle odpadają, ponieważ dyspozycje spływają z różnych części programu wywołującego. Po prostu program startowy dostaje sygnał, że ma uruchomić funkcję wysłania dyspozycji z jakimiś parametrami. Funkcja ta nie jest uruchamiana w innym wątku. Użytkownik sam musi o to zadbać, bo inaczej na czas "obliczeń" główny wątek programu wywołującego jest blokowany. To nie stanowi jakieś problemu jeżeli HttpWebRequest kończy zadanie po 100 ms i nie musiałbym wysyłać zadania do innego wątku, ale przy 1500 ms pracy robi ogromną różnicę.