Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Best Practices for asynchronous calls in MVP with WinForms

I am using the Model-View-Presenter pattern in a WinForms project and one problem (among many) that I am having is when the form tells the presenter to do something and then is un-reactive while the presenter goes of to do it. Fortunately in my project I have no problem with making all presenter calls asynchronous the question is how exactly to do it?

Should each presenter call just be wrapped in a new thread creation?*

new Thread(()=>_presenter.DoSomething()).Start();

What are best-practices here? What if the user presses an "Abort what you're doing" button? How do I abort gracefully?

.* Realistically I would probably just use some sort of a proxy on the presenter to do this rather than putting the thread creation in the WinForm

like image 821
George Mauer Avatar asked May 06 '09 18:05

George Mauer


2 Answers

I usually place any action that can (realistically) take more than a second or two into a separate task, something like:

public interface ITask
{
    void ExecuteTask (ITaskExecutionContext context);
    void AfterSuccess(ITaskExecutionContext context);
    void AfterFailure(ITaskExecutionContext context);
    void AfterAbortion(ITaskExecutionContext context);
}

I also have an abstraction for running such tasks:

public interface ITaskExecutor : IDisposable
{
    void BeginTask(ITask task);
    void TellTaskToStop();
}

One of the implementations of this ITaskExecutor is using the BackgroundWorker:

public class BackgroundTaskExecutor : ITaskExecutor
{
    public void BeginTask(ITask task)
    {
        this.task = task;
        worker = new BackgroundWorker ();
        worker.DoWork += WorkerDoWork;
        worker.RunWorkerCompleted += WorkerRunWorkerCompleted;
        worker.WorkerSupportsCancellation = true;

        worker.RunWorkerAsync();
    }

    ...
}

I heavily rely on the dependency injection and IoC to wire things together. In the presenter then I just call something like:

GoAndDontReturnUntilYouBringMeALotOfMoneyTask task = new GoAndDontReturnUntilYouBringMeALotOfMoneyTask(parameters);
taskExecutor.BeginTask(task);

Cancel/abort buttons are then wired so they tell the task executor / task to abort.

It's actually a bit more complex than presented here, but this is the general idea.

like image 132
Igor Brejc Avatar answered Sep 19 '22 15:09

Igor Brejc


I can only claim that I've thought about this (prior reading your question ;). First I'd rig the places where this actually matters; for example the DB access chokepoint. If there is a place that should not be executed in the "UI" context (you can save it from http://msdn.microsoft.com/en-us/library/system.threading.synchronizationcontext.current.aspx in UI thread and then compare later to non-UI synchronization context) then Debug.BitchAndMoan() about it. Any longer calculations (which "should" be all clearly separated in their own manifolds, right ;) should assert that.

I guess you should at least make the type of execution of presenter function configurable via attribute which is then obeyed by proxy. (just in case you want something done in serial fashion).

Canceling a task is actually presenter's problem, but you must have a reference object that tells what you want to stop. If you go the proxy way, then you could pick up created threads into task-list with IAsyncResult's, but it is still a problem to decide which one is supposed to be canceled if same action is allowed to be called multiple times in parallel. So you must supply the task with a suitable call-specific name when you start it; which implies way too much logic into View side -> Presenter should probably ask View to ask user which one of the tasks should be disposed of.

My experience is that this is usually just worked around by using events (SCSF style). If doing it from scratch, I'd go the proxy way since SCSF has been a pain in so many ways that I doubt its designers' sanity.

like image 31
Pasi Savolainen Avatar answered Sep 20 '22 15:09

Pasi Savolainen