再次运行之前关闭任务

时间:2018-04-06 11:29:01

标签: c# asynchronous task

我致力于实时搜索。此时在属性设置器上有限制编辑文本,我调用一个调用API的方法,然后使用如下所示的结果填充列表:

    private string searchPhrase;
    public string SearchPhrase
    {
        get => searchPhrase;
        set
        {
            SetProperty(ref searchPhrase, value);

            RunOnMainThread(SearchResult.Clear);
            isAllFriends = false;
            currentPage = 0;

            RunInAsync(LoadData);
        }   
    }

    private async Task LoadData()
    {

        var response = await connectionRepository.GetConnections(currentPage, 
    pageSize, searchPhrase);

                foreach (UserConnection uc in response)
                {
                    if (uc.Type != UserConnection.TypeEnum.Awaiting)
                    {
                        RunOnMainThread(() =>
                            SearchResult.Add(new ConnectionUser(uc)));
                    }
                }
    }

但是这种方式完全没用,因为如果文本快速进入,它会完全混搭一个结果列表。所以为了防止这种情况,我想在属性中运行此方法异步,但如果再次更改属性,我想杀死前一个任务并再次为其添加星标。我怎样才能做到这一点?

3 个答案:

答案 0 :(得分:1)

thread的一些信息:

  • 创建一个CancellationTokenSource

    var ctc = new CancellationTokenSource();
    
  • 创建执行异步工作的方法

    private static Task ExecuteLongCancellableMethod(CancellationToken token)
    {
        return Task.Run(() =>
        {
           token.ThrowIfCancellationRequested();
           // more code here
    
           // check again if this task is canceled
           token.ThrowIfCancellationRequested();
    
           // more code
         }
    }
    

在代码中检查取消非常重要。

执行功能:

var cancellable = ExecuteLongCancellableMethod(ctc.Token);

要停止长时间执行,请使用

 ctc.Cancel();

有关详细信息,请参阅链接的主题。

答案 1 :(得分:0)

这个问题可以通过许多不同的方式得到解答。不过IMO我会考虑创建一个

的类
  1. 在执行搜索之前自动延迟X(ms)
  2. 随着搜索请求的变化,能够随时取消。
  3. 实际上这会改变你的代码设计,并且应该封装1和1的逻辑。 2在一个单独的课程中。

    我最初的想法是(并且这些都没有经过测试,主要是伪代码)。

    class ConnectionSearch
    {
        public ConnectionSearch(string phrase, Action<object> addAction)
        {
            _searchPhrase = phrase;
            _addAction = addAction;
            _cancelSource = new CancellationTokenSource();
        }
    
        readonly string _searchPhrase = null;
        readonly Action<object> _addAction;
        readonly CancellationTokenSource _cancelSource;
    
        public void Cancel()
        {
            _cancelSource?.Cancel();
        }
    
        public async void PerformSearch()
        {
            await Task.Delay(300); //await 300ms between keystrokes
            if (_cancelSource.IsCancellationRequested)
                return;
    
            //continue your code keep checking for 
    
            //loop your dataset
            //call _addAction?.Invoke(uc);
        }
    }
    

    这是基本的,实际上只是封装了1点和1点的逻辑。 2,您需要调整代码才能进行搜索。

    接下来,您可以更改您的属性以取消之前运行的实例,然后在下面的内容之后立即启动另一个实例。

    ConnectionSearch connectionSearch;
    string searchPhrase;
    public string SearchPhrase
    {
        get => searchPhrase;
        set
        {
            //do your setter work
            if(connectionSearch != null)
            {
                connectionSearch.Cancel();
            }
            connectionSearch = new ConnectionSearch(value, addConnectionUser);
            connectionSearch.PerformSearch();
        }
    }
    
    void addConnectionUser(object uc)
    {
        //pperform your add logic.. 
    }
    

    代码非常简单,但是您将在setter中看到只是取消现有请求然后创建新请求。你可以放置一些处理清理逻辑,但这应该让你开始。

答案 2 :(得分:0)

你可以实现某种debouncer,它将封装任务结果debouncing的逻辑,即它将确保你运行多个任务,然后只使用最新的任务结果:

public class TaskDebouncer<TResult>
{
    public delegate void TaskDebouncerHandler(TResult result, object sender);
    public event TaskDebouncerHandler OnCompleted;
    public event TaskDebouncerHandler OnDebounced;

    private Task _lastTask;
    private object _lock = new object();

    public void Run(Task<TResult> task)
    {
        lock (_lock)
        {
            _lastTask = task;
        }

        task.ContinueWith(t =>
        {
            if (t.IsFaulted)
                throw t.Exception;

            lock (_lock)
            {
                if (_lastTask == task)
                {
                    OnCompleted?.Invoke(t.Result, this);
                }
                else
                {
                    OnDebounced?.Invoke(t.Result, this);
                }
            }
        });
    }

    public async Task WaitLast()
    {
        await _lastTask;
    }
}

然后,您可以这样做:

private readonly TaskDebouncer<Connections[]> _connectionsDebouncer = new TaskDebouncer<Connections[]>();

public ClassName()
{
    _connectionsDebouncer.OnCompleted += OnConnectionUpdate;
}

public void OnConnectionUpdate(Connections[] connections, object sender)
{
    RunOnMainThread(SearchResult.Clear);

    isAllFriends = false;
    currentPage = 0;

    foreach (var conn in connections)
        RunOnMainThread(() => SearchResult.Add(new ConnectionUser(conn)));
}

private string searchPhrase;
public string SearchPhrase
{
    get => searchPhrase;
    set
    {
        SetProperty(ref searchPhrase, value);

        _connectionsDebouncer.Add(RunInAsync(LoadData));
    }   
}

private async Task<Connection[]> LoadData()
{
    return await connectionRepository
        .GetConnections(currentPage, pageSize, searchPhrase)
        .Where(conn => conn.Type != UserConnection.TypeEnum.Awaiting)
        .ToArray();
}

目前还不清楚RunInAsyncRunOnMainThread方法是什么 我想,你实际上并不需要它们。