Czy jest lepszy wzór oczekiwania na c#?
Znalazłem się w kodowaniu tego typu rzeczy kilka razy.
for (int i = 0; i < 10; i++)
{
if (Thing.WaitingFor())
{
break;
}
Thread.Sleep(sleep_time);
}
if(!Thing.WaitingFor())
{
throw new ItDidntHappenException();
}
To po prostu wygląda jak zły kod, czy jest lepszy sposób na zrobienie tego / czy jest to objaw złego projektu?
9 answers
Znacznie lepszym sposobem implementacji tego wzorca jest wystawienie obiektu Thing
na zdarzenie, na które konsument może czekać. Na przykład a ManualResetEvent
lub AutoResetEvent
. To znacznie upraszcza Twój kod konsumenta, aby był następujący
if (!Thing.ManualResetEvent.WaitOne(sleep_time)) {
throw new ItDidntHappen();
}
// It happened
Kod na stronie Thing
również nie jest tak naprawdę bardziej złożony.
public sealed class Thing {
public readonly ManualResetEvent ManualResetEvent = new ManualResetEvent(false);
private void TheAction() {
...
// Done. Signal the listeners
ManualResetEvent.Set();
}
}
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-08-09 14:26:10
Użyj zdarzeń.
Miej to, na co czekasz, aby wywołać zdarzenie po jego zakończeniu (lub nie udało się go zakończyć w wyznaczonym czasie), a następnie obsłużyć Zdarzenie w głównej aplikacji.
W ten sposób nie masz żadnych Sleep
pętli.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-08-09 13:59:04
Pętla nie jest strasznym sposobem czekania na coś, jeśli program nie ma nic innego do zrobienia podczas oczekiwania (na przykład podczas łączenia się z DB). Widzę jednak pewne problemy z Twoim.
//It's not apparent why you wait exactly 10 times for this thing to happen
for (int i = 0; i < 10; i++)
{
//A method, to me, indicates significant code behind the scenes.
//Could this be a property instead, or maybe a shared reference?
if (Thing.WaitingFor())
{
break;
}
//Sleeping wastes time; the operation could finish halfway through your sleep.
//Unless you need the program to pause for exactly a certain time, consider
//Thread.Yield().
//Also, adjusting the timeout requires considering how many times you'll loop.
Thread.Sleep(sleep_time);
}
if(!Thing.WaitingFor())
{
throw new ItDidntHappenException();
}
W skrócie, powyższy kod wygląda bardziej jak "pętla retry", która została zmodyfikowana, aby działać bardziej jak timeout. Oto jak skonstruowałbym pętlę timeout:
var complete = false;
var startTime = DateTime.Now;
var timeout = new TimeSpan(0,0,30); //a thirty-second timeout.
//We'll loop as many times as we have to; how we exit this loop is dependent only
//on whether it finished within 30 seconds or not.
while(!complete && DateTime.Now < startTime.Add(timeout))
{
//A property indicating status; properties should be simpler in function than methods.
//this one could even be a field.
if(Thing.WereWaitingOnIsComplete)
{
complete = true;
break;
}
//Signals the OS to suspend this thread and run any others that require CPU time.
//the OS controls when we return, which will likely be far sooner than your Sleep().
Thread.Yield();
}
//Reduce dependence on Thing using our local.
if(!complete) throw new TimeoutException();
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-08-09 15:09:56
Jeśli to możliwe, należy zawinąć przetwarzanie asynchroniczne w Task<T>
. To zapewnia najlepsze ze wszystkich światów:
- możesz odpowiedzieć na zakończenie w sposób podobny do zdarzenia, używając kontynuacji zadania.
- możesz poczekać używając rękojeści oczekującej, ponieważ
Task<T>
implementujeIAsyncResult
. - zadania Można łatwo komponować za pomocą
Async CTP
; dobrze też grają zRx
. - zadania mają bardzo czystą wbudowaną obsługę wyjątków system (w szczególności prawidłowo zachowują ślad stosu).
Jeśli chcesz użyć timeoutu, RX lub asynchroniczny CTP może to zapewnić.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-08-09 14:19:56
Rzuciłbym okiem na klasę WaitHandle. W szczególności klasa ManualResetEvent , która czeka aż obiekt zostanie ustawiony. Możesz również określić dla niego wartości limitu czasu i sprawdzić, czy został on ustawiony później.
// Member variable
ManualResetEvent manual = new ManualResetEvent(false); // Not set
// Where you want to wait.
manual.WaitOne(); // Wait for manual.Set() to be called to continue here
if(!manual.WaitOne(0)) // Check if set
{
throw new ItDidntHappenException();
}
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-08-09 14:05:33
Wywołanie Thread.Sleep
zawsze jest aktywnym czekaniem, którego należy unikać.
Alternatywą byłoby użycie timera. Dla łatwiejszego użycia, możesz zamknąć to w klasie.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-08-09 13:59:35
Zazwyczaj odradzam rzucanie WYJĄTKÓW.
// Inside a method...
checks=0;
while(!Thing.WaitingFor() && ++checks<10) {
Thread.Sleep(sleep_time);
}
return checks<10; //False = We didn't find it, true = we did
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-08-09 13:59:42
Myślę, że powinieneś użyć AutoResetEvents. Działają świetnie, gdy czekasz na kolejny wątek, aby zakończyć zadanie
Przykład:
AutoResetEvent hasItem;
AutoResetEvent doneWithItem;
int jobitem;
public void ThreadOne()
{
int i;
while(true)
{
//SomeLongJob
i++;
jobitem = i;
hasItem.Set();
doneWithItem.WaitOne();
}
}
public void ThreadTwo()
{
while(true)
{
hasItem.WaitOne();
ProcessItem(jobitem);
doneWithItem.Set();
}
}
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-08-09 14:10:09
Oto jak możesz to zrobić z System.Threading.Tasks
:
Task t = Task.Factory.StartNew(
() =>
{
Thread.Sleep(1000);
});
if (t.Wait(500))
{
Console.WriteLine("Success.");
}
else
{
Console.WriteLine("Timeout.");
}
Ale jeśli z jakiegoś powodu nie możesz używać zadań (jak wymóg. Net 2.0), możesz użyć ManualResetEvent
, Jak wspomniano w odpowiedzi Jaredpara lub użyć czegoś takiego:
public class RunHelper
{
private readonly object _gate = new object();
private bool _finished;
public RunHelper(Action action)
{
ThreadPool.QueueUserWorkItem(
s =>
{
action();
lock (_gate)
{
_finished = true;
Monitor.Pulse(_gate);
}
});
}
public bool Wait(int milliseconds)
{
lock (_gate)
{
if (_finished)
{
return true;
}
return Monitor.Wait(_gate, milliseconds);
}
}
}
Przy podejściu Wait / Pulse nie tworzysz jawnie zdarzeń, więc nie musisz przejmować się ich usuwaniem.
Przykład użycia:
var rh = new RunHelper(
() =>
{
Thread.Sleep(1000);
});
if (rh.Wait(500))
{
Console.WriteLine("Success.");
}
else
{
Console.WriteLine("Timeout.");
}
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-01-23 17:02:12