Есть ли лучший шаблон ожидания для С#?
Я обнаружил, что несколько раз кодирую этот тип.
for (int i = 0; i < 10; i++)
{
if (Thing.WaitingFor())
{
break;
}
Thread.Sleep(sleep_time);
}
if(!Thing.WaitingFor())
{
throw new ItDidntHappenException();
}
Он просто выглядит как плохой код, есть ли лучший способ сделать это/это симптом плохого дизайна?
Ответы
Ответ 1
Лучше всего реализовать этот шаблон, чтобы ваш объект Thing
отображал событие, на которое потребитель может подождать. Например, ManualResetEvent
или AutoResetEvent
. Это значительно упрощает ваш потребительский код следующим образом
if (!Thing.ManualResetEvent.WaitOne(sleep_time)) {
throw new ItDidntHappen();
}
// It happened
Код на стороне Thing
также не является более сложным.
public sealed class Thing {
public readonly ManualResetEvent ManualResetEvent = new ManualResetEvent(false);
private void TheAction() {
...
// Done. Signal the listeners
ManualResetEvent.Set();
}
}
Ответ 2
Использовать события.
Ожидайте, когда вы закончите (или не закончите в течение выделенного времени), а затем обработайте событие в своем основном приложении.
Таким образом, у вас нет циклов Sleep
.
Ответ 3
Цикл не является TERRIBLE способом ждать чего-то, если нет ничего, что могла бы сделать ваша программа во время ожидания (например, при подключении к БД). Однако я вижу некоторые проблемы с вашими.
//It 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();
}
Короче говоря, вышеприведенный код больше похож на "петлю повтора", которая была унаследована, чтобы работать больше как тайм-аут. Вот как бы я построил цикл таймаута:
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();
Ответ 4
Если возможно, выполните асинхронную обработку, завернутую в Task<T>
. Это обеспечивает лучшее из всех миров:
- Вы можете ответить на завершение в виде событий, используя продолжения задач.
- Вы можете подождать, используя обработчик завершения, потому что
Task<T>
реализует IAsyncResult
.
- Задачи легко скомпилируются с помощью
Async CTP
; они также хорошо сочетаются с Rx
.
- Задачи имеют очень чистую встроенную систему обработки исключений (в частности, они правильно сохраняют трассировку стека).
Если вам нужно использовать таймаут, тогда Rx или Async CTP могут предоставить это.
Ответ 5
Я бы посмотрел на класс WaitHandle. В частности, класс ManualResetEvent, который ждет, пока объект не будет установлен. Вы также можете указать для него значения тайм-аута и проверить, установлено ли это позже.
// 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();
}
Ответ 6
Вызов Thread.Sleep
всегда является активным ожиданием, которого следует избегать.
Один из вариантов - использовать таймер. Для более простого использования вы можете инкапсулировать это в класс.
Ответ 7
Я обычно отговариваю бросать исключения.
// 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
Ответ 8
Я думаю, вы должны использовать AutoResetEvents. Они отлично работают, когда вы ожидаете, что еще один поток завершит задачу.
Пример:
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();
}
}
Ответ 9
Вот как вы можете это сделать с помощью System.Threading.Tasks
:
Task t = Task.Factory.StartNew(
() =>
{
Thread.Sleep(1000);
});
if (t.Wait(500))
{
Console.WriteLine("Success.");
}
else
{
Console.WriteLine("Timeout.");
}
Но если вы не можете использовать Задачи по какой-либо причине (например, требование .Net 2.0), вы можете использовать ManualResetEvent
, как указано в ответе JaredPar, или использовать что-то вроде этого:
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);
}
}
}
С помощью подхода Wait/Pulse вы явно не создаете события, поэтому вам не нужно заботиться об их удалении.
Пример использования:
var rh = new RunHelper(
() =>
{
Thread.Sleep(1000);
});
if (rh.Wait(500))
{
Console.WriteLine("Success.");
}
else
{
Console.WriteLine("Timeout.");
}