I'm using a custom ContentProvider. For querying, there is a CancellationSignal (API 16+) which can be used to cancel a previous call to query().
My question: How can I archive that with delete()? For clarification, my custom provider manages files on SD card, and so I want to be able to cancel delete operation inside my provider.
I solved this with a simple solution.
For example, for every call to query(), we put a parameter pointing to the task ID, and use a SparseBooleanArray to hold that ID, like:
...
private static final SparseBooleanArray _MapInterruption = new SparseBooleanArray();
...
#Override
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
Cursor cursor = ...
int taskId = ... // obtain task ID from uri
boolean cancel = ... // obtain cancellation flag from uri
if (cancel) {
_MapInterruption.put(taskId, true);
return null; // don't return any cursor
} else {
doQuery(taskId);
if (_MapInterruption.get(taskId)) {
_MapInterruption.delete(taskId);
return null; // because the task was cancelled
}
}
...
return cursor;
}// query()
private void doQuery(int taskId) {
while (!_MapInterruption.get(taskId)) {
... // do the task here
}
}// doQuery()
Usage:
To query:
...
getContentResolver().query("content://your-uri?task_id=1", ...);
To cancel:
...
getContentResolver().query("content://your-uri?task_id=1&cancel=true", ...);
For a complete working solution, have a look at android-filechooser.
The advantage is you can use this technique in Android… 1+ and for other methods such as delete(), update()... While CancellationSignal is only available in API 16+ and is limited to only query().
Related
I am using MudBlazor to develop a client-side Blazor-WASM app. I have a search field which is to update the bound variable in real-time (i.e. not only after losing focus, but while typing). Furthermore, I want to clear the MudTextField's text if user presses the -key (so they don't have to use the clear-button if they don't want to). Well, if I do it like this, it works perfectly:
<MudTextField Label="search something" Variant="Variant.Text" Clearable="true"
#bind-Value="SearchText" Immediate="true"
#onkeydown="e => KeyboardEventHandler(e)"
/>
#code {
public string SearchText { get; set; }
private void KeyboardEventHandler(KeyboardEventArgs args)
{
if (args.Code == "Escape") { SearchText = string.Empty; }
}
}
This works like a charm. However, I need the component to wait a little while before updating the variable, because each change triggers a new filtering process that may block the UI for a bit (since BlazorWASM, at the time, doesn't support multithreading). So, I use DebounceInterval, i. e. do it like this:
<MudTextField Label="search something" Variant="Variant.Text" Clearable="true"
#bind-Value="SearchText" Immediate="true" DebounceInterval="350"
#onkeydown="e => KeyboardEventHandler(e)"
/>
#code {
public string SearchText { get; set; }
private void KeyboardEventHandler(KeyboardEventArgs args)
{
if (args.Code == "Escape") { SearchText = string.Empty; }
}
}
If I do it like this, when typing faster than the debounce interval, only the last typed character is kept (i.e. every character typed replaces the one before it), as long as the #onkeydown event is used, too.
How can I make this work? I'd be grateful for a possible solution or ideas towards it.
Thanks for taking the time to read this.
There are a few ways to implement a debouncer. The simplest is just to do a backoff of say 300ms before doing your lookup. This gives you a 300ms + your lookup period.
Here's a slightly different method that uses Tasks and waits either the backoff period or the query period whichever is the longer.
The deboucer with lots of comment code to explain what's happening.
public sealed class ActionLimiter
{
private int _backOffPeriod = 0;
private Func<Task> _taskToRun;
private Task _activeTask = Task.CompletedTask;
private TaskCompletionSource<bool>? _queuedTaskCompletionSource;
private TaskCompletionSource<bool>? _activeTaskCompletionSource;
private async Task RunQueueAsync()
{
// if we have a completed task then null it
if (_activeTaskCompletionSource is not null && _activeTaskCompletionSource.Task.IsCompleted)
_activeTaskCompletionSource = null;
// if we have a running task then everything is already in motion and there's nothing to do
if (_activeTaskCompletionSource is not null)
return;
// run the loop while we have a queued request.
while (_queuedTaskCompletionSource is not null)
{
// assign the queued task reference to the running task
_activeTaskCompletionSource = _queuedTaskCompletionSource;
// And release the reference
_queuedTaskCompletionSource = null;
// start backoff task
var backoffTask = Task.Delay(_backOffPeriod);
// start main task
var mainTask = _taskToRun.Invoke();
// await both ensures we run the backoff period or greater
await Task.WhenAll( new Task[] { mainTask, backoffTask } );
// Set the running task completion as complete
_activeTaskCompletionSource.TrySetResult(true);
// and release our reference to the running task completion
// The originator will still hold a reference and can act on it's completion
_activeTaskCompletionSource = null;
// back to the top to check if another task has been queued
}
return;
}
public Task<bool> QueueAsync()
{
var oldCompletionTask = _queuedTaskCompletionSource;
// Create a new completion task
var newCompletionTask = new TaskCompletionSource<bool>();
// get the actual task before we assign it to the queue
var task = newCompletionTask.Task;
// replace _queuedTaskCompletionSource
_queuedTaskCompletionSource = newCompletionTask;
// check if we already have a queued queued task.
// If so set it as completed, false = not run
if (oldCompletionTask is not null && !oldCompletionTask.Task.IsCompleted)
oldCompletionTask?.TrySetResult(false);
// if we don't have a running task or the task is complete , then there's no process running the queue
// So we need to call it and assign it to `runningTask`
if (_activeTask is null || _activeTask.IsCompleted)
_activeTask = this.RunQueueAsync();
// return the reference to the task we queued
return task;
}
private ActionLimiter(Func<Task> toRun, int backOffPeriod)
{
_backOffPeriod = backOffPeriod;
_taskToRun = toRun;
}
/// <summary>
/// Static method to create a new deBouncer
/// </summary>
/// <param name="toRun">method to run to update the component</param>
/// <param name="backOffPeriod">Back off period in millisecs</param>
/// <returns></returns>
public static ActionLimiter Create(Func<Task> toRun, int backOffPeriod)
=> new ActionLimiter(toRun, backOffPeriod > 300 ? backOffPeriod : 300);
}
And a demo page to show how to use it:
#page "/"
#using Blazr.UI
<h3>Test</h3>
<input class="form-control mb-2" type="text" #oninput=OnInput />
<div class="alert alert-info">
#message
</div>
#code {
private ActionLimiter _limiter;
private string? message;
private string? value;
public Test()
=> _limiter = ActionLimiter.Create(this.RunTask, 300);
private async Task OnInput(ChangeEventArgs e)
{
value = e.Value?.ToString() ?? null;
await _limiter.QueueAsync();
}
private async Task RunTask()
{
// simulate doing your search
await Task.Delay(1000);
message = value;
}
}
There's a full CodeProject article here describing this technique and how to build a Typeahead control using it.
https://www.codeproject.com/Articles/5351256/Building-a-Blazor-Autocomplete-Control
I'm investigating an issue with Esper 5.5.0. In the code base which I'm working on, an "INSERT INTO" statement is used and it pulls out data with EPStatement#iterator() from the "INSERT INTO" statement. It does return a non-empty Iterator (which looks weird to me though).
The issue is that the Iterator keeps accumulating data and never gets cleaned up. I'm trying to find a way to clean up the data in the Iterator but I don't know how I can do that. Its remove() method throws an Exception and deleting data from the derived window doesn't have any effect on the EPStatement object which corresponds to the "INSERT INTO" statement. How can I clean up the data in the Iterator which corresponds to the "INSERT INTO" statement? (EDIT: Not the one corresponds to the derived window, the one for the "INSERT INTO" statement itself)
Unfortunately I'm even unable to create a simple reproducer. They do something like the following but the Iterator is always empty when I try to replicate that behavior in new code. I would also like to know what is missing to replicate the behavior.
public class MyTest {
#Test
void eplStatementReturningNonEmptyIterator() {
EPServiceProvider engine = EPServiceProviderManager.getDefaultProvider();
EPRuntime rt = engine.getEPRuntime();
EPAdministrator adm = engine.getEPAdministrator();
adm.getConfiguration().addEventType(PersonEvent.class);
adm.createEPL("create window PersonEventWindow.win:keepall() as PersonEvent");
EPStatement epl = adm.createEPL("insert into PersonEventWindow select irstream * from PersonEvent");
rt.sendEvent(new PersonEvent("foo", 1));
rt.sendEvent(new PersonEvent("bar", 2));
// passes, but this question is not about this one
assert count(rt.executeQuery("select * from PersonEventWindow").iterator()) == 2;
// This question is about this one, I want to clean up the Iterator which epl.iterator() returns
assert count(epl.iterator()) == 2;
// (this assertion ^ fails actually; I cannot even replicate the issue)
}
private static int count(Iterator<?> it) {
int count = 0;
while (it.hasNext()) {
it.next();
count++;
}
return count;
}
public static class PersonEvent {
private String name;
private int age;
public PersonEvent(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public int getAge() {
return age;
}
}
}
This code creates named window PersonEventWindow#keepall that keeps all events that ever arrived (its what keepall means). The code executes a fire-and=forget query rt.executeQuery that selects all events from the named window and the iterator returned provides each event. Iterators I don't think allow remove. One option, use a time window that automatically removes data from the named window like for example PersonEventWindow#time(10) which keeps only the last 10 seconds. Another option, execute a fire-and-forget query like rt.executeQuery("delete from PersonEventWindow") and that deletes all events from the named window.
It turned out that the Iterator for "insert into..." returns elements if it selects from a window. In order to clean up the Iterator, we can delete data from the window which the "insert into" query selects data from.
The following code verifies my explanation I believe:
public class MyTest3 {
EPServiceProvider engine;
EPAdministrator epa;
EPRuntime epr;
#BeforeEach
void setUp() {
engine = EPServiceProviderManager.getDefaultProvider();
epa = engine.getEPAdministrator();
epr = engine.getEPRuntime();
}
#Test
#DisplayName("The Iterator gets cleaned up by delete from MyWindow")
void cleanUpIterator() {
epa.getConfiguration().addEventType(MyEvent.class);
epa.createEPL("create window MyWindow.std:unique(id) as MyEvent");
epa.createEPL("insert into MyWindow select id from MyEvent");
epr.sendEvent(new MyEvent(1));
epr.sendEvent(new MyEvent(2));
EPStatement insertIntoAnotherWindow = epa.createEPL("insert into AnotherWindow select id from MyWindow");
assertThat(count(insertIntoAnotherWindow.iterator())).isEqualTo(2); // this returns the events surprisingly
epr.executeQuery("delete from MyWindow");
assertThat(count(insertIntoAnotherWindow.iterator())).isEqualTo(0); // now it's empty
}
public static class MyEvent {
private final int id;
public MyEvent(int id) {
this.id = id;
}
public int getId() {
return id;
}
}
#AfterEach
void tearDown() {
engine.destroy();
}
private static int count(Iterator<?> it) {
int count = 0;
while (it.hasNext()) {
it.next();
count++;
}
return count;
}
}
To prevent the XY problem, I'll start from the beginning:
I have a non-blocking SOAP client which I wrapped it to make the return type Mono<T> (By default it accepts callback. I can elaborate on this if needed).
Now I want to do (given ID):
1. Get the code by ID
2. Do something with the code
3. After that, get Foo and Bar and create FooBar
What I wrote was:
public class MyService {
private final MySoapClient soapClient;
public Mono<FooBarDto> doSomething(String id) {
return Mono.just(id)
.flatMap(soapClient::getCode) // returns Mono<String>
.flatMap(code ->
soapClient.doSomething(code) // returns Mono<Void>
.then(getFooBar(id, code))); // See this
}
private Mono<FooBarDto> getFooBar(String id, String code) {
return Mono.zip(
soapClient.getFoo(code), // returns Mono<Foo>
soapClient.getBar(code) // returns Mono<Bar>
).map(tuple2 -> toFooBarDto(id, tuple2));
}
private FooBarDto toFooBarDto(String id, Tuple2<Foo, Bar> tuple2) {
return FooBarDto.builder()/* set properties */.build();
}
}
Now the problem is, because methods of the SOAP client are not lazy (the moment you call them they start the process), the semantic of then won't work here. Meaning I want to get Foo and Bar when doSomething is done. They all start together.
I tried to change it fix it by changing then to flatMap, but made it even worse. The getFooBar never got called. (1. Can someone please explain why?).
So what I ended up doing was to wrap SOAP calls again to make them lazy:
public class MySoapClient {
private final AutoGeneratedSoapClient client;
Mono<Foo> getFoo(GetFooRequest request) {
return Mono.just(request).flatMap(this::doGetMsisdnByIccid);
}
private Mono<Foo> doGetFoo(GetFooRequest request) {
val handler = new AsyncHandler<GetFooRequest>();
client.getFoo(request, handler);
return Mono.fromFuture(handler.future);
}
private static class AsyncHandler<T> implements javax.xml.ws.AsyncHandler<T> {
private final CompletableFuture<T> future = new CompletableFuture<>();
#Override
public void handleResponse(Response<T> res) {
try {
future.complete(res.get());
} catch (Exception e) {
future.completeExceptionally(e);
}
}
}
}
Is there any better way to do it? Specifically:
2. Using CompeletableFuture and the callback.
3. Making methods lazy in the SOAP client.
I tried to change it fix it by changing then to flatMap, but made it
even worse. The getFooBar never got called. (1. Can someone please
explain why?)
I think a Mono<Void> always completes empty (or error), so subsequent flatMap is never called.
Using CompeletableFuture and the callback.
Making methods lazy in the SOAP client.
To make the call lazy you can do one of the followings:
1, You can use Mono.fromFuture which accepts a supplier:
private Mono<Foo> doGetFoo(GetFooRequest request) {
return Mono.fromFuture(() -> {
val handler = new AsyncHandler<GetFooRequest>();
client.getFoo(request, handler);
return handler.future;
});
}
2, You can use Mono.defer:
private Mono<Foo> doGetFoo(GetFooRequest request) {
return Mono.defer(() -> {
val handler = new AsyncHandler<GetFooRequest>();
client.getFoo(request, handler);
return Mono.fromFuture(handler.future);
});
}
3, You can get rid of CompletableFuture and use Mono.create instead, something like this:
private Mono<Foo> doGetFoo(GetFooRequest request) {
return Mono.create(sink -> {
AsyncHandler<Foo> handler = response ->
{
try
{
sink.success(response.get());
} catch (Exception e)
{
sink.error(e);
}
};
client.getFoo(request, handler);
});
}
If you do any of these it will be safe to use then method and it will work as expected.
I am creating a batch data streamer in apache ignite, and need to control what happening after data receive.
My batch has a structure:
public class Batch implements Binarylizable, Serializable {
private String eventKey;
private byte[] bytes;
etc..
Then i trying to stream my data:
try (IgniteDataStreamer<Integer, Batch> streamer = serviceGrid.getIgnite().dataStreamer(cacheName);
StreamBatcher batcher = StreamBatcherFactory.create(event) ){
streamer.receiver(StreamTransformer.from(new BatchDataProcessor(event)));
streamer.autoFlushFrequency(1000);
streamer.allowOverwrite(true);
statusService.updateStatus(event.getKey(), StatusType.EXECUTING);
int counter = 0;
Batch batch = null;
IgniteFuture<?> future = null;
while ((batch = batcher.batch()) != null) {
future = streamer.addData(counter++, batch);
}
Object getted = future.get();
Just for test use lets get only the last future, and try to analyze this object. In the code above I'm using BatchDataProcessor, that look like this:
public class BatchDataProcessor implements CacheEntryProcessor<Integer, Batch, Object> {
private final Event event;
private final String eventKey;
public BatchDataProcessor(Event event) {
this.event = event;
this.eventKey = event.getKey();
}
#Override
public Object process(MutableEntry<Integer, Batch> mutableEntry, Object... objects) throws EntryProcessorException {
Node node = NodeIgniter.node(Ignition.localIgnite().cluster().localNode().id());
ServiceGridContainer container = (ServiceGridContainer) node.getEnvironmentContainer().getContainerObject(ServiceGridContainer.class);
ProcessMarshaller marshaller = (ProcessMarshaller) container.getService(ProcessMarshaller.class);
LocalProcess localProcess = marshaller.intoProccessing(event.getLambdaExecutionKey());
try {
localProcess.addBatch(mutableEntry);
} catch (IOException e) {
e.printStackTrace();
} finally {
return new String("111");
}
}
}
So after localProcess.addBatch(mutableEntry) I want to send back an information about the status of this particular batch, so I think that I should do this in IgniteFuture object, but I don't find any information how to control the future object that's received in addData function.
Can anybody help with understanding, where can I control future that receives in addData function or some other way to realize a callback to streamed batch?
When you do StreamTransformer.from(), you forfeit the result of your BatchDataProcessor, because
for (Map.Entry<K, V> entry : entries)
cache.invoke(entry.getKey(), this, entry.getValue());
// ^ result of cache.invoke() is discarded here
DataStreamer is for one-directional streaming of data. It is not supposed to return values as far as I know.
If you depend on the result of cache.invoke(), I recommend calling it directly instead of relying on DataStreamer.
BTW, be careful with fut.get(). You should do dataStreamer.flush() first, or DataStreamer's futures will wait indefinitely.
I'm writing a Fragment that uses a loader to get a Cursor containing data about locations of various things on a map. I've inherited code to sort these locations by distance from the device, or from a search location; distance metrics aren't something that's particularly easy to implement in SQL, so rather than use a CursorAdapter (as elsewhere) I'm loading the data once from the Cursor and then sorting it afterwards.
I have just one problem: when the web service returns a new set of locations (for example, on first load), the list isn't updating. I've registered a ContentObserver on the Cursor and it is being hit when I call notifyChange(...) in the ContentProvider; it's just that the Cursor I've stored from the original load still has a count of zero.
The callbacks and the ContentObserver look like this:
private LoaderCallbacks<Cursor> mCallbacks = new LoaderCallbacks<Cursor>() {
public void onLoaderReset(Loader<Cursor> loader) {
mLoaderCreated = false;
mCursor.unregisterContentObserver(mObserver);
mCursor = null;
}
public void onLoadFinished(Loader<Cursor> loader, Cursor cursor) {
if(cursor!=mCursor) {
if(mCursor!=null)
mCursor.unregisterContentObserver(mObserver);
cursor.registerContentObserver(mObserver);
mCursor = cursor;
if(cursor.isClosed()) {
getLoaderManager().restartLoader(mFragmentId, null, mCallbacks);
return;
}
}
mDataModel.populate(cursor);
}
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
mLoaderCreated = true;
triggerServicesFeed();
CursorLoader cursorLoader = null;
if(id == mFragmentId) {
cursorLoader = new CursorLoader(getActivity(),
FerrariVertuContentProvider.SERVICES_URI,
null, null, null,null);
}
return cursorLoader;
}
};
private ContentObserver mObserver = new ContentObserver(null) {
public void onChange(boolean selfChange, android.net.Uri uri) {
onChange(selfChange);
};
public void onChange(boolean selfChange) {
if(mCursor.isClosed()) {
mCursor.unregisterContentObserver(this);
mCursor = null;
} else {
getActivity().runOnUiThread(new Runnable() {
#Override
public void run() {
//mCursor still reports zero on first run
mDataModel.populate(mCursor);
}
});
}
};
};
I know CursorAdapter just updates when the Cursor updates, and the fact that I'm getting update events when I'd expect to makes me think this stage of the process, at least, is working. How do I either get mCursor to give me the new data, or get a fresh Cursor representing the new data?