I'm writing a UI where the user can type in a search term and a list get continuously updated offering suggestions.
My first though was that the Rx primitive Throttle was a perfect match but it gets me half there.
The suggestions take a while to fetch so I get them asynchronously on not on the UI thread.
The problem is that I want to discard/skip/throw away a result if the user types af the throttle time span again.
For example:
Time starts and the user presses a key : 0ms
The throttle is set to 100ms.
The fetch takes 200ms.
At 150ms the user pressed another key
Now with Throttle the first fetch will still go ahead an populate the gui suggestion list.
What I like to learn is how can I cancel that first fetch as it is not relevant anymore?
Only the second keypress should trigger an update to the gui.
Here is what I tried
(I use ReactiveUI but the Q is about Rx)
public IEnumerable<usp_getOpdrachtgevers_Result> Results { get; set; } // [Reactive] pu
public SearchOpdrachtgeverVM()
{
this.WhenAnyValue(x => x.FirstName,
x => x.LastName
)
.Throttle(TimeSpan.FromMilliseconds(200))
.Subscribe(async vm => Results = await PopulateGrid());
}
private async Task<IEnumerable<usp_getOpdrachtgevers_Result>> PopulateGrid()
{
return await Task.Run(
() => _opdrachtgeversCache
.Where(x =>
x.vNaam.Contains(FirstName)
&& x.vLastName.Contains(LastName)
)
);
}
If you turn your async Task into an Observable, this looks like a classic use for Switch:
this.WhenAnyValue(x => x.FirstName,
x => x.LastName
)
.Throttle(TimeSpan.FromMilliseconds(100))
.Select(l => PopulateGrid().ToObservable())
.Switch()
.Subscribe(vm => Results = vm);
Throttle should be used to suppress calls while the user is typing. So adjust that TimeSpan as you like.
If I understand what you want correctly, this can be done in a quite straight forward manner and clean if you refactor your code slightly.
Firstly, make the first name and last name triggers into observables. In the code below, I have used subjects but it is better if you're able to use static Observable methods to 'convert' them into observables; e.g. Observable.FromEvent.
Then turn the code to fetch results into an observable. In the code below I have used Observable.Create to return a stream of IEnumerable<string>.
Finally, you can use the Switch operator to subscribe to each new GetResults call and cancel the previous call to GetResults.
Sounds complicated but the code is quite straight forward:
private Subject<string> _firstName = new Subject<string>();
private Subject<string> _lastName = new Subject<string>();
private Task<IEnumerable<string>> FetchResults(string firstName, string lastName, CancellationToken cancellationToken)
{
// Fetch the results, respecting the cancellation token at the earliest opportunity
return Task.FromResult(Enumerable.Empty<string>());
}
private IObservable<IEnumerable<string>> GetResults(string firstName, string lastName)
{
return Observable.Create<IEnumerable<string>>(
async observer =>
{
// Use a cancellation disposable to provide a cancellation token the the asynchronous method
// When the subscription to this observable is disposed, the cancellation token will be cancelled.
CancellationDisposable disposable = new CancellationDisposable();
IEnumerable<string> results = await FetchResults(firstName, lastName, disposable.Token);
if (!disposable.IsDisposed)
{
observer.OnNext(results);
observer.OnCompleted();
}
return disposable;
}
);
}
private void UpdateGrid(IEnumerable<string> results)
{
// Do whatever
}
private IDisposable ShouldUpdateGridWhenFirstOrLastNameChanges()
{
return Observable
// Whenever the first or last name changes, create a tuple of the first and last name
.CombineLatest(_firstName, _lastName, (firstName, lastName) => new { FirstName = firstName, LastName = lastName })
// Throttle these tuples so we only get a value after it has settled for 100ms
.Throttle(TimeSpan.FromMilliseconds(100))
// Select the results as an observable
.Select(tuple => GetResults(tuple.FirstName, tuple.LastName))
// Subscribe to the new results and cancel any previous subscription
.Switch()
// Use the new results to update the grid
.Subscribe(UpdateGrid);
}
Quick tip: you should really pass an explicit scheduler into the Throttle so that you can effectively unit test this code using the TestScheduler.
Hope it helps.
Related
I have the following code, that I intend to run asynchronously. My goal is that GetPictureForEmployeeAsync() is called in parallel as many times as needed. I'd like to make sure that 'await' on CreatePicture does not prevent me from doing so.
public Task<Picture[]> GetPictures(IDictionary<string, string> tags)
{
var query = documentRepository.GetRepositoryQuery();
var employees = query.Where(doc => doc.Gender == tags["gender"]);
return Task.WhenAll(employees.Select(employee => GetPictureForEmployeeAsync(employee, tags)));
}
private Task<Picture> GetPictureForEmployeeAsync(Employee employee, IDictionary<string, string> tags)
{
var base64PictureTask = blobRepository.GetBase64PictureAsync(employee.ID.ToString());
var documentTask = documentRepository.GetItemAsync(employee.ID.ToString());
return CreatePicture(tags, base64PictureTask, documentTask);
}
private static async Task<Picture> CreatePicture(IDictionary<string, string> tags, Task<string> base64PictureTask, Task<Employee> documentTask)
{
var document = await documentTask;
return new Picture
{
EmployeeID = document.ID,
Data = await base64PictureTask,
ID = document.ID.ToString(),
Tags = tags,
};
}
If I understand it correctly, Task.WhenAll() is not affected by the two awaited tasks inside CreatePicture() because GetPictureForEmployeeAsync() is not awaited. Am I right about this? If not, how should I restructure the code to achieve what I want?
I'd like to make sure that 'await' on CreatePicture does not prevent me from doing so.
It doesn't.
If I understand it correctly, Task.WhenAll() is not affected by the two awaited tasks inside CreatePicture() because GetPictureForEmployeeAsync() is not awaited. Am I right about this?
Yes and no. The WhenAll isn't limited in any way by the awaited tasks in CreatePicture, but that has nothing to do with whether GetPictureForEmployeeAsync is awaited or not. These two lines of code are equivalent in terms of behavior:
return Task.WhenAll(employees.Select(employee => GetPictureForEmployeeAsync(employee, tags)));
return Task.WhenAll(employees.Select(async employee => await GetPictureForEmployeeAsync(employee, tags)));
I recommend reading my async intro to get a good understanding of how async and await work with tasks.
Also, since GetPictures has non-trivial logic (GetRepositoryQuery and evaluating tags["gender"]), I recommend using async and await for GetPictures, as such:
public async Task<Picture[]> GetPictures(IDictionary<string, string> tags)
{
var query = documentRepository.GetRepositoryQuery();
var employees = query.Where(doc => doc.Gender == tags["gender"]);
var tasks = employees.Select(employee => GetPictureForEmployeeAsync(employee, tags)).ToList();
return await Task.WhenAll(tasks);
}
As a final note, you may find your code cleaner if you don't pass around "tasks meant to be awaited" - instead, await them first and pass their result values:
async Task<Picture> GetPictureForEmployeeAsync(Employee employee, IDictionary<string, string> tags)
{
var base64PictureTask = blobRepository.GetBase64PictureAsync(employee.ID.ToString());
var documentTask = documentRepository.GetItemAsync(employee.ID.ToString());
await Task.WhenAll(base64PictureTask, documentTask);
return CreatePicture(tags, await base64PictureTask, await documentTask);
}
static Picture CreatePicture(IDictionary<string, string> tags, string base64Picture, Employee document)
{
return new Picture
{
EmployeeID = document.ID,
Data = base64Picture,
ID = document.ID.ToString(),
Tags = tags,
};
}
The thing to keep in mind about calling an async method is that, as soon as an await statement is reached inside that method, control immediately goes back to the code that invoked the async method -- no matter where the await statement happens to be in the method. With a 'normal' method, control doesn't go back to the code that invokes that method until the end of that method is reached.
So in your case, you can do the following:
private async Task<Picture> GetPictureForEmployeeAsync(Employee employee, IDictionary<string, string> tags)
{
// As soon as we get here, control immediately goes back to the GetPictures
// method -- no need to store the task in a variable and await it within
// CreatePicture as you were doing
var picture = await blobRepository.GetBase64PictureAsync(employee.ID.ToString());
var document = await documentRepository.GetItemAsync(employee.ID.ToString());
return CreatePicture(tags, picture, document);
}
Because the first line of code in GetPictureForEmployeeAsync has an await, control will immediately go right back to this line...
return Task.WhenAll(employees.Select(employee => GetPictureForEmployeeAsync(employee, tags)));
...as soon as it is invoked. This will have the effect of all of the employee items getting processed in parallel (well, sort of -- the number of threads that will be allotted to your application will be limited).
As an additional word of advice, if this application is hitting a database or web service to get the pictures or documents, this code will likely cause you issues with running out of available connections. If this is the case, consider using System.Threading.Tasks.Parallel and setting the maximum degree of parallelism, or use SemaphoreSlim to control the number of connections used simultaneously.
I'm introducing asynchronous programming to my existing code base and having some trouble with calling Select() on the result of GetStudents() - the error message received is as follows "Task<List<ApplicationUser>> does not contain a defintion for Select". I think it'll be due to incorrect syntax, but any guidance would be appreciated - thanks.
public async Task<List<ApplicationUser>> GetStudents()
{
return await Task.Run(() => _context.Users.ToList());
}
public async Task<StudentIndexViewModel> CreateStudentRegisterViewModel()
{
var model = new StudentIndexViewModel();
var students = await _studentRepo.GetStudents().
Select(x => new StudentViewModel
{
Forename = x.Forename,
Surname = x.Surname
}).ToListAsync();
model.Students = students;
return model;
}
As it was mentioned, the error comes from trying to call Select on a Task<T>, which is not valid. However, the problem is much bigger than that. The code is currently getting the entire table from the database just to get a few values from the result in-memory. This is a waste of processing time in both the database and the application server.
Not only that, but also using a thread pool thread just to wait on an I/O operation is another waste.
Overall, the code should be something like this.
public async Task<List<ApplicationUser>> GetApplicationUsersAsync()
{
// use Entity Framework properly with ToListAsync
// this returns the entire table
return await _context.Users.ToListAsync();
}
public async Task<List<StudentViewModel>> GetStudentsAsync()
{
// use Entity Framework properly with ToListAsync
return await _context.Users
// this only returns the 2 needed properties
.Select(x => new StudentViewModel
{
Forename = x.Forename,
Surname = x.Surname
})
.ToListAsync();
}
public async Task<StudentIndexViewModel> CreateStudentRegisterViewModel()
{
var model = new StudentIndexViewModel();
model.Students = await _studentRepo.GetStudentsAsync();
return model;
}
_studentRepo.GetStudents() returns a Task<List<...>>.
As the error is telling you, Task isn't a collection and doesn't have a Select() method.
You can use await to get the collection inside the task, but you need to call Select() on the awaited value (your code currently awaits Select()).
You need to add parentheses:
(await ...).Select(...);
I am looking at using IObservable to get a response in a request-response environment within a c# async methods and replace some older callback based code, but I am finding that if a value is pushed (Subject.OnNext) to the observable but FirstAsync is not yet at the await, then the FirstAsync is never given that message.
Is there a straightforward way to make it work, without a 2nd task/thread plus synchronisation?
public async Task<ResponseMessage> Send(RequestMessage message)
{
var id = Guid.NewGuid();
var ret = Inbound.FirstAsync((x) => x.id == id).Timeout(timeout); // Never even gets invoked if response is too fast
await DoSendMessage(id, message);
return await ret; // Will sometimes miss the event/message
}
// somewhere else reading the socket in a loop
// may or may not be the thread calling Send
Inbound = subject.AsObservable();
while (cond)
{
...
subject.OnNext(message);
}
I cant put the await for the FirstAsync simply before I send the request, as that would prevent the request being sent.
The await will subscribe to the observable. You can separate the subscription from the await by calling ToTask:
public async Task<ResponseMessage> Send(RequestMessage message)
{
var id = Guid.NewGuid();
var ret = Inbound.FirstAsync((x) => x.id == id).Timeout(timeout).ToTask();
await DoSendMessage(id, message);
return await ret;
}
I took a closer look and there is a very easy solution to your problem by just converting hot into cold observable. Replace Subject with ReplaySubject. Here is the article: http://www.introtorx.com/content/v1.0.10621.0/14_HotAndColdObservables.html.
Here is the explanation:
The Replay extension method allows you take an existing observable
sequence and give it 'replay' semantics as per ReplaySubject. As a
reminder, the ReplaySubject will cache all values so that any late
subscribers will also get all of the values.
I am new the using Task.Run() along with async and await to make UI more responsive, so likely I have not implemented something correctly.
I have reviewed the great articles from Stephen Cleary about using AsyncCommands and have used his code from Patterns for Asynchronous MVVM Applications: Commands as a basis for having a responsive UI but when I run the code it still seems to freeze up (I am not able to move the window or interact with other buttons until the function has fully finished.
I am trying to perform a search which usually takes 5-10 seconds to return. Below is the code that creates the AsyncCommand along with the what the function does.
Code:
public ICommand SearchCommand
{
get
{
if (_SearchCommand == null)
{
_SearchCommand = AsyncCommand.Create(() => Search());
}
return _SearchCommand;
}
}
private async Task Search()
{
IEnumerable<PIPoint> points = await SearchAsync(_CurrentPIServer, NameSearch, PointSourceSearch).ConfigureAwait(false);
SearchResults.Clear();
foreach (PIPoint point in points)
{
SearchResults.Add(point.Name);
}
}
private async Task<IEnumerable<PIPoint>> SearchAsync(string Server, string NameSearch, string PointSourceSearch)
{
{
PIServers KnownServers = new PIServers();
PIServer server = KnownServers[Server];
server.Connect();
return await Task.Run<IEnumerable<PIPoint>>(()=>PIPoint.FindPIPoints(server, NameSearch, PointSourceSearch)).ConfigureAwait(false);
}
}
I am thinking that the issue is somewhere in how I am pushing the long running function onto a thread and its not getting off of the UI thread or my understanding of how Tasks and async/await are completely off.
EDIT 1:
Following Stephen's answer I updated the functions, but I am not seeing any change in the UI responsiveness. I created a second command that performs the same actions and I get the same response from UI in either case. The code now looks like the following
CODE:
public ICommand SearchCommand
{
get
{
if (_SearchCommand == null)
{
_SearchCommand = AsyncCommand.Create(async () =>
{
var results = await Task.Run(()=>Search(_CurrentPIServer, NameSearch, PointSourceSearch));
SearchResults = new ObservableCollection<string>(results.Select(x => x.Name));
});
}
return _SearchCommand;
}
}
public ICommand SearchCommand2
{
get
{
if (_SearchCommand2 == null)
{
_SearchCommand2 = new RelayCommand(() =>
{
var results = Search(_CurrentPIServer, NameSearch, PointSourceSearch);
SearchResults = new ObservableCollection<string>(results.Select(x => x.Name));
}
,()=> true);
}
return _SearchCommand2;
}
}
private IEnumerable<PIPoint> Search(string Server, string NameSearch, string PointSourceSearch)
{
PIServers KnownServers = new PIServers();
PIServer server = KnownServers[Server];
server.Connect();
return PIPoint.FindPIPoints(server, NameSearch, PointSourceSearch);
}
I must be missing something but I am not sure what at this point.
EDIT 2:
After more investigation on what was taking so long it turns out the iterating of the list after the results are found is what was hanging the process. By simply changing what the Search function was returning and having it already iterated over the list of objects allows for the UI to remain responsive. I marked Stephen's answer as correct as it handled my main problem of properly moving work off of the UI thread I just didnt move the actual time consuming work off.
My first guess is that the work queued to Task.Run is quite fast, and the delay is caused by other code (e.g., PIServer.Connect).
Another thing of note is that you are using ConfigureAwait(false) in Search which updates SearchResults - which I suspect is wrong. If SearchResults is bound to the UI, then you should be in the UI context when updating it, so ConfigureAwait(false) should not be used.
That said, there's a Task.Run principle that's good to keep in mind: push Task.Run as far up your call stack as possible. I explain this in more detail on my blog. The general idea is that Task.Run should be used to invoke synchronous methods; it shouldn't be used in the implementation of an asynchronous method (at least, not one that is intended to be reused).
As a final note, async is functional in nature. So it's more natural to return results than update collections as a side effect.
Combining these recommendations, the resulting code would look like:
private IEnumerable<PIPoint> Search(string Server, string NameSearch, string PointSourceSearch)
{
PIServers KnownServers = new PIServers();
PIServer server = KnownServers[Server];
// TODO: If "Connect" or "FindPIPoints" are naturally asynchronous,
// then this method should be converted back to an asynchronous method.
server.Connect();
return PIPoint.FindPIPoints(server, NameSearch, PointSourceSearch);
}
public ICommand SearchCommand
{
get
{
if (_SearchCommand == null)
{
_SearchCommand = AsyncCommand.Create(async () =>
{
var results = await Task.Run(() =>
Search(_CurrentPIServer, NameSearch, PointSourceSearch));
SearchResults = new ObservableCollection<string>(
results.Select(x => x.Name));
});
}
return _SearchCommand;
}
}
I'm trying to use Reactive Extensions (Rx) for a task where it seems to be a good fit, polling at a specific interval a web service and display its last x results.
I have a web service that sends me the status of an instrument I want to monitor. I would like to poll this instrument at a specific rate and display in a list the last 20 status that have been polled.
So my list would be like a "moving window" of the service result.
I'm developing a WPF app with Caliburn.Micro, but I don't think this is very relevant.
What I managed to get until now is the following (just a sample app that I hacked quickly, I'm not going to do this in the ShellViewModel in the real app):
public class ShellViewModel : Caliburn.Micro.PropertyChangedBase, IShell
{
private ObservableCollection<string> times;
private string currentTime;
public ShellViewModel()
{
times = new ObservableCollection<string>();
Observable
.Interval(TimeSpan.FromSeconds(1))
.SelectMany(x => this.GetCurrentDate().ToObservable())
.ObserveOnDispatcher()
.Subscribe(x =>
{
this.CurrentTime = x;
this.times.Add(x);
});
}
public IEnumerable<string> Times
{
get
{
return this.times;
}
}
public string CurrentTime
{
get
{
return this.currentTime;
}
set
{
this.currentTime = value;
this.NotifyOfPropertyChange(() => this.CurrentTime);
}
}
private async Task<string> GetCurrentDate()
{
var client = new RestClient("http://www.timeapi.org");
var request = new RestRequest("/utc/now.json");
var response = await client.ExecuteGetTaskAsync(request);
return response.Content;
}
}
In the view I have just a label bound to the CurrentTime property and a list bound to the Times property.
The issue I have is:
It's not limited to the 20 items in the list as I always add items to the ObservableCollection but I can't find a better way to databind
The Interval doesn't work as I'd like. If the querying takes more than 1 second to run, two queries will be run in parallel, which I'd like not to happen. My goal would be that the query repeats indefinitely but at a pace of no more than 1 query every seconds. If a query makes more than 1 second to end, it should wait for it to have finish and directly trigger the new query.
Second edit:
Previous edit below was me being stupid and very confused, it triggers events continuously because Interval is something continuous that never ends. Brandon's solution is correct and works as expected.
Edit:
Based on Brandon's example, I tried to do the following code in LinqPad:
Observable
.Merge(Observable.Interval(TimeSpan.FromSeconds(2)), Observable.Interval(TimeSpan.FromSeconds(10)))
.Repeat()
.Scan(new List<double>(), (list, item) => { list.Add(item); return list; })
.Subscribe(x => Console.Out.WriteLine(x))
And I can see that the write to the console occurs every 2 seconds, and not every 10. So the Repeat doesn't wait for both Observable to be finished before repeating.
Try this:
// timer that completes after 1 second
var intervalTimer = Observable
.Empty<string>()
.Delay(TimeSpan.FromSeconds(1));
// queries one time whenever subscribed
var query = Observable.FromAsync(GetCurrentDate);
// query + interval timer which completes
// only after both the query and the timer
// have expired
var intervalQuery = Observable.Merge(query, intervalTimer);
// Re-issue the query whenever intervalQuery completes
var queryLoop = intervalQuery.Repeat();
// Keep the 20 most recent results
// Note. Use an immutable list for this
// https://www.nuget.org/packages/microsoft.bcl.immutable
// otherwise you will have problems with
// the list changing while an observer
// is still observing it.
var recentResults = queryLoop.Scan(
ImmutableList.Create<string>(), // starts off empty
(acc, item) =>
{
acc = acc.Add(item);
if (acc.Count > 20)
{
acc = acc.RemoveAt(0);
}
return acc;
});
// store the results
recentResults
.ObserveOnDispatcher()
.Subscribe(items =>
{
this.CurrentTime = items[0];
this.RecentItems = items;
});
This should skip the interval messages while a GetCurrentDate is in Progress.
Observable
.Interval(TimeSpan.FromSeconds(1))
.GroupByUntil(p => 1,p => GetCurrentDate().ToObservable().Do(x => {
this.CurrentTime = x;
this.times.Add(x);
}))
.SelectMany(p => p.LastAsync())
.Subscribe();