我致力于实时搜索。此时在属性设置器上有限制编辑文本,我调用一个调用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)));
}
}
}
但是这种方式完全没用,因为如果文本快速进入,它会完全混搭一个结果列表。所以为了防止这种情况,我想在属性中运行此方法异步,但如果再次更改属性,我想杀死前一个任务并再次为其添加星标。我怎样才能做到这一点?
答案 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和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();
}
目前还不清楚RunInAsync
和RunOnMainThread
方法是什么
我想,你实际上并不需要它们。