JavaFX2: Can I pause a background Task / Service? - multithreading

I am trying to set up a background service that would perform bulk loading of transaction data from a csv file. This background service would be initiated from a menu item action mapped to a method in the controller/presenter class.
Ever so often, some data turns up in the csv file for which no master data can be found in the database, this would normally cause the upload to choke and fail.
On such occasions, I would like to be able to have the background service pause its processing and invoke a dialog from a presenter class to take in user input. The user input would be used to add a master row in the database, after which the background service should resume from where it had left off (not from the beginning of the csv file, but from the row which caused the error).
Is this possible to achieve in JavaFX, perhaps with the javafx.concurrent API? How would I go about doing this?

Solution
When your background process encounters a situation where it requires a user to be prompted for input, use FutureTask executed in Platform.runLater to showAndWait the dialog prompt on the JavaFX application thread. In the background process use futureTask.get to pause the background process until the user has input the necessary values which will allow the process to continue.
Sample Code Snippet
Here is the essence of code for this approach which can be placed inside the call method of your background process:
String nextText = readLineFromSource();
if ("MISSING".equals(nextText)) {
updateMessage("Prompting for missing text");
FutureTask<String> futureTask = new FutureTask(
new MissingTextPrompt()
);
Platform.runLater(futureTask);
nextText = futureTask.get();
}
...
class MissingTextPrompt implements Callable<String> {
private TextField textField;
#Override public String call() throws Exception {
final Stage dialog = new Stage();
dialog.setScene(createDialogScene());
dialog.showAndWait();
return textField.getText();
}
...
}
Sample Application
I created a small, complete sample application to demonstrate this approach.
The output of the sample application is:
Sample Output Explanation
Lines read without missing values are just plain brown.
Lines with a prompt value entered have a pale green background.
Fourteen lines have been read, the background task has already paused once at the 6th line which was missing a value. The user was prompted for the missing value (to which the user entered xyzzy), then the process continued until line 14 which is also missing and the background task is again paused and another prompt dialog is being displayed.

Related

How to handle watchOS CoreData background save correctly?

My watchOS app uses core data for local storage. Saving the managed context is done in background:
var backgroundContext = persistentContainer.newBackgroundContext()
//…
backgroundContext.perform {
//…
let saveError = self.saveManagedContext(managedContext: self.backgroundContext)
completion(saveError)
}
//…
func saveManagedContext(managedContext: NSManagedObjectContext) -> Error? {
if !managedContext.hasChanges { return nil }
do {
try managedContext.save()
return nil
} catch let error as NSError {
return error
}
}
Very rarely, my context is not saved. One reason I can think of is the following:
After my data are changed, I initiate a background core data context save operation.
But before the background task starts, the watch extension is put by the user into background, and is then terminated by watchOS.
This probably also prevents the core data background save to execute.
My questions are:
- Is this scenario possible?
- If so, what would be the correct handling of a core data background context save?
PS: On the iOS side, I do the same, but here it is possible to request additional background processing time using
var bgTask: UIBackgroundTaskIdentifier = application.beginBackgroundTask(expirationHandler: {
//…
application.endBackgroundTask(bgTask)
}
By now, I think I can answer my question:
If the watch extension is put by the user into background, the extension delegate calls applicationDidEnterBackground(). The docs say:
The system typically suspends your app shortly after this method
returns; therefore, you should not call any asynchronous methods from
your applicationDidEnterBackground() implementation. Asynchronous
methods may not be able to complete before the app is suspended.
I think this also applies to background tasks that have been initiated before, so it is actually possible that a core data background save does not complete.
Thus, the core data save should be done on the main thread. My current solution is the following:
My background context is no longer set up using persistentContainer.newBackgroundContext(), since such a context is connected directly to the persistentContainer, and when this context is saved, changes are written to the persistent store, which may take relatively long. Instead, I now set up the background context by
var backgroundContext = NSManagedObjectContext.init(concurrencyType: .privateQueueConcurrencyType)
and set its parent property as
backgroundContext.parent = container.viewContext
where container is the persistent container. Now, when the background context is saved, it is not written to the persistent store, but to its parent, the view content that is handled by the main thread. Since this saving is only done in memory, it is pretty fast.
Additionally, in applicationDidEnterBackground() of the extension delegate, I save the view context. Since this is done on the main thread, The docs say:
The applicationDidEnterBackground() method is your last chance to
perform any cleanup before the app is terminated.
In normal circumstances, enough time should be provided by watchOS. If not, other docs say:
If needed, you can request additional background execution time by
calling the ProcessInfo class’s
performExpiringActivity(withReason:using:) method.
This is probably equivalent to setting up a background task in iOS as shown in my question.
Hope this helps somebody!

COMException^ on FilePicker PickSingleFileAsync() call

I'm trying to make a game (Universal DX11 application) and at some point I need access to image library to allow user to select avatar. But for some reason call of PickSingleFileAsync on picker rises an exception.
Windows::Storage::Pickers::FileOpenPicker^ openPicker = ref new Windows::Storage::Pickers::FileOpenPicker();
openPicker->SuggestedStartLocation = Windows::Storage::Pickers::PickerLocationId::PicturesLibrary;
openPicker->ViewMode = Windows::Storage::Pickers::PickerViewMode::Thumbnail;
// Filter to include a sample subset of file types.
auto filters = openPicker->FileTypeFilter;
filters->Clear();
filters->Append(".png");
openPicker->PickSingleFileAsync();// same exception with create_task(...);
Seems like the sample works only if I put it into UI thread. How can I use picker from my own thread?
UPD: HRESULT:0x80004005
Ok, I just decided to call dipatcher's RunAsync to execute this code. But I still have no idea why I cannot open picker inside non-UI thread.

Lightswitch task on a background thread

I have a lightswitch app which sends an email when a new job is added. I added a sendemail helper class which is called when the record is inserted.
This works however the interface hangs on save waiting for the email to be sent.
I would like to perform this asychronously so that the user can go on and do his thing while the email sends in the background.
I've tried creating a new thread in the inserted part, hoping it would spin off the thread and then return to the user, but it doesn't work, it is still waiting for the thread to finish.
Thread caseemail = new Thread(new ParameterizedThreadStart(newSendmail.generateCaseEmail));
string[] paramsToPass = new String[] { entity.ProjectNumber, entity.CreatedBy, entity.TheProjectClientManagerFixed, entity.ProjectName };
caseemail.Start(paramsToPass);
How should I be doing this?
So in the end this code works, the errors were from problems access dataworkspaces from the other thread, which is obviously not allowed.
I will leave this here as an example of how to spin off a task into the background from lightswitch, leaving the interface responsive while lightswitch goes away and does something else.

MFC: Update Ribbon Bar elements to reflect operation progress

I am making a simulation tool, that runs simulation (in a separate thread) over user defined number of iterations, which can be entered in an Edit control on the Ribbon Bar. I would like to reuse it to show current iteration during simulation. I also also put CMFCRibbonProgressBar to show the progress. The Ribbon Bar is created with resource editor.
The question is what is the what to get the progress bar and iteration counter to get timely updated without causing the GUI to become unresponsive?
The conventional way over ON_UPDATE_COMMAND_UI routines requires activity in the window, like moving the mouse.
So I probably need a thread that would update this controls. Things like simply creating a thread and trying to update the controls from or using concurrency::parallel_invoke are not suitable.The former simply doesn't work, the latter works, but causes GUI to freeze.
I store pointers in my document to simplify access to the controls. https://stackoverflow.com/a/25429446?noredirect=1
My general idea is (pseudocode)
beginUpdatingThread()
{
while(simulating)
{
updateEditControl();
updateProgressBar();
sleep_40_ms();//conserves the resorces as there is no sense to update more frequent than 25 times per second
}
}
What is correct way of implementing this?
ASSERT(m_hWnd!=NULL);
MSG msg;
while (simulating)
{
// Handle dialog messages
while(PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
{
if(!IsDialogMessage(&msg))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
}
I solved this by adding a method to the main window that performs the update. now the thread updating from above continually post messages to the main window to perform the update:
auto h = static_cast<CMainFrame*>(AfxGetMainWnd())->m_hWnd;
//here code for starting simulation in a separate thread
std::thread updating([this,h]{
while (simulating)
{
::PostMessage(h, WM_UPDATE_VISUALS, sumulator.getCurrentIteration(), 0);
std::this_thread::sleep_for(std::chrono::milliseconds(40));
}
::PostMessage(h, WM_UPDATE_VISUALS, num_iterations, 0);
});
updating.detach()
I made a subtle mistake at first by capturing h by reference, which quickly expires
But in the end, the above code does exactly what I wanted to achieve

Print multiple jobs without interruption c#

Is there a way to print multiple jobs in a row, without letting another user send a print job in between? (sort of "don't give the token to another user as long as my print jobs haven't finished")
It's a shared printer and many users have access to it, and what I'm printig is a big document so it takes some time;
I'm using more then one job because its pages are not to be printed from the same paper tray, so I have to switch the paper source in my code.
Help please! and thanks in advance.
P.S. I'm using the PrintDocument object of .Net
Instead of using multiple jobs, I can just change the settings for each page when printing (modify the PrintPageEventHandler), here's a link, and a sample of my code:
private void PrintPage(object sender, PrintPageEventArgs ev)
{
using (Metafile pageImage = new Metafile(streamList[currentPageIndex]))
{
// If it's the first page
if (currentPageIndex == 0)
{
// Use a certain tray
ev.PageSettings.PaperSource = PaperTrayPage1;
}
// For the rest of the document
else
{
// Use another tray
ev.PageSettings.PaperSource = PaperTrayRest;
}
currentPageIndex++;
ev.Graphics.DrawImage(pageImage, ev.PageBounds);
ev.HasMorePages = (currentPageIndex < streamList.Count);
}
}
Combining multiple jobs (PrintDocument objects) is possible, here's an example.
There isn't, as far are I know, you'd need to either keep the printer busy by keeping the job open and sending PJL or other commands that don't eject a page. The other option is to concatenate your jobs together into one large job. I guess you could also programatically pause the shared print queue and send the data directly using a direct IP print port or something. In the end there isn't an elegant solution to this.

Resources