简体   繁体   中英

.NET client-side WCF with queued requests

Background

I'm working on updating legacy software library. The legacy code uses an infinitely looping System.Threading.Thread that executes processes in the queue. These processes perform multiple requests with another legacy system that can only process one request at a time.

I'm trying to modernize, but I'm new to WCF services and there may be a big hole in my knowledge that'd simplify things.

WCF Client-Side Host

In modernizing, I'm trying to move to a client-side WCF service. The WCF service allows requests to be queued from multiple a applications. The service takes a request and returns a GUID back so that I can properly associate via the callbacks.

public class SomeService : ISomeService
{
    public Guid AddToQueue(Request request)
    {
    // Code to add the request to a queue, return a Guid, etc.
    }
}

public interface ISomeCallback
{
    void NotifyExecuting(Guid guid)
    void NotifyComplete(Guid guid)
    void NotifyFault(Guid guid, byte[] data)
}

WCF Client Process Queues

The problem I'm having is that the legacy processes can include more than one request. Process 1 might do Request X then Request Y, and based on those results follow up with Request Z. With the legacy system, there might be Processes 1-10 queued up.

I have a cludgy model where the process is executed. I'm handling events on the process to know when it's finished or fails. But, it just feels really cludgy...

public class ActionsQueue
{
    public IList<Action> PendingActions { get; private set; }
    public Action CurrentAction { get; private set; }

    public void Add(Action action)
    {
        PendingAction.Add(action)
        if (CurrentAction is null)
            ExecuteNextAction();
    }

    private void ExecuteNextAction()
    {
        if (PendingActions.Count > 0)
        {
            CurrentAction = PendingActions[0];
            PendingActions.RemoveAt(0);
            CurrentAction.Completed += OnActionCompleted;
            CurrentAction.Execute();
        }
    }

    private OnActionCompleted(object sender, EventArgs e)
    {
        CurrentAction = default;
        ExecuteNextAction();
    }
}

public class Action
{
    internal void Execute()
    {
    // Instantiate the first request
    // Add handlers to the first request
    // Send it to the service
    }

    internal void OnRequestXComplete()
    {
    // Use the data that's come back from the request
    // Proceed with future requests
    }
}

With the client-side callback the GUID is matched up to the original request, and it raises a related event on the original requests. Again, the implementation here feels really cludgy.

I've seen example of Async methods for the host, having a Task returned, and then using an await on the Task. But, I've also seen recommendations not to do this.

Any recommendations on how to untangle this mess into something more usable are appreciated. Again, it's possible that there's a hole in my knowledge here that's keeping me from a better solutiong.

Thanks

Queued communication between the client and the server of WCF is usually possible using a NetMsmqbinding, which ensures persistent communication between the client and the server. See this article for specific examples.

If you need efficient and fast message processing, use a non-transactional queue and set the ExactlyOnce attribute to False, but this has a security impact. Check this docs for further info.

In case anyone comes along later with a similar issue, this is a rough sketch of what I ended up with:

[ServiceContract(Name="MyService", SessionMode=Session.Required]
public interface IMyServiceContract
{
    [OperationContract()]
    Task<string> ExecuteRequestAsync(Action action);
}

public class MyService: IMyServiceContract
{
    private TaskQueue queue = new TaskQueue();
    public async Task<string> ExecuteRequestAsync(Request request)
    {
        return await queue.Enqueue(() => request.Execute());
    }
}

public class TaskQueue
{
    private SemaphoreSlim semaphore;
public TaskQueue()
    {
        semaphore = new SemaphoreSlim(1);
    }

    Task<T> Enqueue<T>(Func<T> function)
    {
        await semaphore.WaitAsync();
        try
        {
            return await Task.Factory.StartNew(() => function.invoke();)
        }
        finally
    {
            semaphore.Release();
        }
    }
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM