Question

I have MVP application C#, .NET 4, WinForms. It uses Bridge class which communicate with third party app via NamedPipe. The command flow is like this: View → Presenter → Manager → Bridge → Client And back in the reverse order. View is prepared for multitasking. I split reverse chain in Manager by rising event with the result, but it doesn't help.

// View class
public void AccountInfo_Clicked() { presenter.RequestAccountInfo(); }

public void UpdateAccountInfo(AccountInfo info)
{
    if (pnlInfo.InvokeRequired)
        pnlInfo.BeginInvoke(new InfoDelegate(UpdateAccountInfo), new object[] {info});
    else
        pnlInfo.Update(info);
}

// Presenter class
public void RequestAccountInfo() { manager.RequestAccountInfo(); }

private void Manager_AccountInfoUpdated(object sender, AccountInfoEventArgs e)
{
    view.UpdateAccountInfo(e.AccountInfo);
}

// Manager class
public void RequestAccountInfo()
{
    AccountInfo accountInfo = bridge.GetAccountInfo();
    OnAccountInfoUpdated(new AccountInfoEventArgs(accountInfo));
}

// Bridge class
public AccountInfo GetAccountInfo() { return client.GetAccountInfo(); }

// Client class
public AccountInfo GetAccountInfo()
{
    string respond = Command("AccountInfo");
    return new AccountInfo(respond);
}

private string Command(string command)
{
    var pipe = new ClientPipe(pipeName);
    pipe.Connect();
    return pipe.Command(command);
}

I want to unfreeze the UI during command processing. There are also other commands that can be executed. Finally all commands reach Command(string command) method in Client.

I tried to break the chain in Manager by using task and ContinueWith but it results to pipe failing to connect. The reason is that client is not thread safe.

// Manager class
public void RequestAccountInfo()
{
    var task = Task<AccountInfo>.Factory.StartNew(() => bridge.GetAccountInfo());
    task.ContinueWith(t => { OnAccountInfoUpdated(new AccountInfoEventArgs(t.Result)); });
}

My question is: Where to use Task, ContinueWith and where to Lock?

I assume I can lock only Command(string command) because it is the ultimate method.

private string Command(string command)
{
    lock (pipeLock)
    {
        var pipe = new ClientPipe(pipeName);
        pipe.Connect();
        return pipe.Command(command);
    }
}

Can I use Task, Wait in Command in Client class?

Was it helpful?

Solution 2

I locked Command in Client class. It appears that it works perfectly in that way. No blocking UI, no pipe errors. I lock on pipeName because each copy of View is using a unique pipe name.

I applied Task<Type>, ContinueWith to all commands in Manager class.

// Manager class
public void RequestSomeInfo()
{
    var task = Task<SomeInfo>.Factory.StartNew(() => bridge.GetSomeInfo());
    task.ContinueWith(t => { OnInfoUpdated(new InfoEventArgs(t.Result)); });
}

// Client class
private string Command(string command)
{
    lock (pipeName)
    {
        var pipe = new ClientPipe(pipeName);
        pipe.Connect();
        return pipe.Command(command);
    }
}

OTHER TIPS

I think the problem you are having is that bridge.GetAccountInfo() is trying to extract information from the UI itself - hence the UI thread. This code

public void RequestAccountInfo()
{
    var task = Task<AccountInfo>.Factory.StartNew(() => bridge.GetAccountInfo());
    task.ContinueWith(t => { OnAccountInfoUpdated(new AccountInfoEventArgs(t.Result)); });
}

is attempting to execute the bridge.GetAccountInfo() method (accessing the UI) from a background thread-pool thread.

My first question here would be how expensive is the call to bridge.GetAccountInfo()? If it is not expensive, it makes no sense to put working into multi-threading this aspect. If it is expensive, you will have to think about a way to make this operation thread safe (I can't advise without more information).

Another thing to do would assess the expense of a move to WCF. This handles most synchronisation problems for you... I am sorry I can't be of more help. I wrote the above before I read your last comment.

I hope this is of some use.


Aside: something to be aware of is SynchronizationContext. Using a TaskScheduler you can launch a Task on the UI thread (this is not what you want here as this again will just block the UI - however, this can be good to know when reporting [in .NET 4.0]. To launch your code above on the UI thread you can do

public void RequestAccountInfo()
{
    var task = Task<AccountInfo>.Factory.StartNew(() => 
        bridge.GetAccountInfo(), 
        TaskScheduler.FromCurrentSynchronizationContext());
    task.ContinueWith(t => { OnAccountInfoUpdated(new AccountInfoEventArgs(t.Result)); });
}
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top