Как запустить Задачи так, чтобы побеждает последняя запущенная (не завершенная)? - PullRequest
1 голос
/ 29 мая 2020

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

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

До сих пор я придумал следующий класс:

public class LastAddedTaskStrategy<T>
{
    private DateTime latest;

    public T Result { get; private set; }

    public async Task Add(Task<T> t)
    {
        var timestamp = DateTime.Now;
        latest = timestamp;

        var currentResult = await t;

        if (timestamp >= latest)
            Result = currentResult;
    }
}

Идея состоит в том, что вы можете добавить (и ожидать) как много Задач, сколько вы хотите (например, поиск), но побеждают только результаты последней добавленной (не последней выполненной) Задачи. Таким образом, если предыдущий поиск завершился после более позднего поиска, свойство Results не будет обновлено, потому что это старый результат.

Это хорошее решение или есть что-то получше?

1 Ответ

1 голос
/ 29 мая 2020

Вот метод расширения для System.Windows.Forms элементов управления, который подписывается на их событие TextChanged. Каждый раз, когда событие запускается, вызывается асинхронный метод, и результат этого метода передается обработчику. Результат распространяется только при условии, что асинхронный метод не будет вытеснен до его завершения.

public static void OnTextChangedExecute<TResult>(this Control control,
    Func<CancellationToken, Task<TResult>> function,
    Action<TResult> handler)
{
    CancellationTokenSource activeCTS = null;
    control.TextChanged += Event_Handler;

    async void Event_Handler(object sender, EventArgs args)
    {
        activeCTS?.Cancel();
        TResult result;
        using (var cts = new CancellationTokenSource())
        {
            activeCTS = cts;
            try
            {
                result = await function(cts.Token);
                cts.Token.ThrowIfCancellationRequested();
            }
            catch (OperationCanceledException)
                when (cts.Token.IsCancellationRequested)
            {
                return; // Preempted, don't invoke the handler.
            }
            finally
            {
                if (activeCTS == cts) activeCTS = null;
            }
        }
        handler(result);
    }
}

Пример использования:

public Form1()
{
    InitializeComponent();
    TextBox1.OnTextChangedExecute(
        ct => SearchAsync(TextBox1.Text, ct), TextBox1_SearchCompleted);
}

async Task<int> SearchAsync(string text, CancellationToken token)
{
    await Task.Delay(1000, token); // Simulate some cancelable I/O operation
    return Int32.Parse(text);
}

void TextBox1_SearchCompleted(int result)
{
    MessageBox.Show($"Result: {result}");
}

Метод OnTextChangedExecute не является поточным. сейф. Он предназначен для вызова только из потока пользовательского интерфейса. Это зависит от наличия WindowsFormsSynchronizationContext для правильной работы.

...