Подтвердить что ты не робот

Dispatcher Invoke (...) против BeginInvoke (...) путаницы

Я смущен, почему я не могу заставить это приложение счетчика тестов работать с 2 (или более) одновременными запусками countertextboxes с использованием "BeginInvoke" в моем диспетчере в методе Count().

Вы можете решить проблему, заменив BeginInvoke на Invoke. Но это не решает мою путаницу.

Вот пример кода, о котором я говорю:

public class CounterTextBox : TextBox
{
    private int _number;

    public void Start()
    {
        (new Action(Count)).BeginInvoke(null, null);
    }

    private void Count()
    {
        while (true)
        {
            if (_number++ > 10000) _number = 0;
            this.Dispatcher.BeginInvoke(new Action(UpdateText), System.Windows.Threading.DispatcherPriority.Background, null);    
        }
    }

    private void UpdateText()
    {
        this.Text = "" + _number;
    }
}
4b9b3361

Ответ 1

Когда вы используете Dispatcher.BeginInvoke, это означает, что он планирует выполнение данного действия для выполнения в потоке пользовательского интерфейса в более поздний момент времени, а затем возвращает элемент управления, чтобы позволить текущему потоку продолжать выполнение. Invoke блокирует вызывающего абонента до завершения запланированного действия.

Когда вы используете BeginInvoke, ваш цикл будет работать быстрее супер, так как BeginInvoke сразу возвращается. Это означает, что вы добавляете много и много действий в очередь сообщений. Вы добавляете их намного быстрее, чем их можно обработать. Это означает, что существует много времени между тем, когда вы планируете сообщение и когда он действительно получает шанс на запуск.

Фактическое действие, которое вы используете, использует поле _number. Но _number очень быстро изменяется другим потоком и пока действие находится в очереди. Это означает, что он не отображает значение _number в момент, когда вы запланировали действие, но скорее то, что оно есть после того, как оно продолжается в нем очень плотно.

Если вы используете Dispatcher.Invoke вместо этого, это предотвращает "опережение" цикла и наличие нескольких запланированных событий, что гарантирует, что значение, которое он пишет, всегда является "текущим" значением. Кроме того, заставляя каждую итерацию цикла ждать выполнения сообщения, он делает цикл намного менее "плотным", поэтому он не может работать так быстро, как обычно.

Если вы хотите использовать BeginInvoke, первое, что вам действительно нужно сделать, это замедлить ваш цикл. Если вы хотите, чтобы он обновлял текст каждую секунду или даже 10 мс или что-то еще, вы можете использовать Thread.Sleep для ожидания соответствующего времени.

Затем вам нужно взять копию _number, прежде чем передавать ее в Dispatcher, чтобы она отображала значение в то время, когда вы его запланировали, а не во время его выполнения:

while (true)
{
    if (_number++ > 10000)
        _number = 0;
    int copy = _number;
    this.Dispatcher.BeginInvoke(new Action(() => UpdateText(copy))
        , System.Windows.Threading.DispatcherPriority.Background, null);
    Thread.Sleep(200);
}

private void UpdateText(int number)
{
    this.Text = number.ToString();
}