views:

502

answers:

3

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

A: 

Why not make the proxy pattern you use accept a couple of call backs to return the results or abort?

Preet Sangha
I am sorry, but this in no way answers my question. I am asking what the best practices are for running calls asynchronously? New Thread? BackgroundWorker? How to specifically do an abort? I do not need to worry about callbacks, this is traditional MVP so the presenter never actually returns anything.
George Mauer
+1  A: 

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.

Pasi Savolainen
Not too many problems with chokepoints in my application, only 5 or 6 different places where a user interaction gets routed to a presenter and they can all afford to be asynchronous. How about how to actually spin the new thread? new Thread? BackgroundWorker? Something else?
George Mauer
I _think_ BW is meant more for a known task that always does "same" thing, so spawning thread should be idiomatic .NET here. Depending on what you do you may want to use threadpool: http://msdn.microsoft.com/en-us/library/ms973903.aspx . By default threadpool is 25 deep, so you may look into configuring/testing that (this limit does not apply if you simply always spawn a new thread)
Pasi Savolainen
+1  A: 

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.

Igor Brejc
I would pass in lambdas for Execute, AfterSuccess, AfterFailure, AfterAbortion but other than that, good advice.
George Mauer
Yes, you can do it with lambdas, but that would be useful for simple tasks only. More complex task logic merits its own class. Also, you don't really need to implement all of the "After" methods, so it would be best to implement a base abstract class with virtual methods which you can override if you need to.
Igor Brejc