开发者

Async WCF: wait for another call

开发者 https://www.devze.com 2023-03-14 01:08 出处:网络
We have an old Silverlight UserControl + WCF component in our framework and we would like to increase the reusability of this feature. The component should work with basic functionality by default, bu

We have an old Silverlight UserControl + WCF component in our framework and we would like to increase the reusability of this feature. The component should work with basic functionality by default, but we would like to extend it based on the current project (without modifying the original, so more of this control can appear in the full system with different functionality).

So we made a plan, where everything looks great, except one thing. Here is a short summary:

Silverlight UserControl can be extended and manipulated via ContentPresenter at the UI and ViewModel inheritance, events and messaging in the client logic.

Back-end business logic can be manipulated with module loading.

This gonna be okay I think. For example you can disable/remove fields from the UI with overriden ViewModel properties, and at the back-end you can avoid some action with custom modules.

The interesting part is when 开发者_StackOverflowyou add new fields via the ContentPresenter. Ok, you add new properties to the inherited ViewModel, then you can bind to them. You have the additional data. When you save base data, you know it's succeeded, then you can start saving your additional data (additional data can be anything, in a different table at back-end for example). Fine, we extended our UserControl and the back-end logic and the original userControl still doesn't know anything about our extension.

But we lost transaction. For example we can save base data, but additional data saving throws an exception, we have the updated base data but nothing in the additional table. We really doesn't want this possibility, so I came up with this idea:

One WCF call should wait for the other at the back-end, and if both arrived, we can begin cross thread communication between them, and of course, we can handle the base and the additional data in the same transaction, and the base component still doesn't know anything about the other (it just provide a feature to do something with it, but it doesn't know who gonna do it).

I made a very simplified proof of concept solution, this is the output:

1 send begins

Press return to send the second piece

2 send begins

2 send completed, returned: 1

1 send completed, returned: 2

Service

namespace MyService
{
    [ServiceContract]
    [ServiceBehavior(ConcurrencyMode = ConcurrencyMode.Multiple)]
    public class Service1
    {

        protected bool _sameArrived;

        protected Piece _same;

        [OperationContract]
        public Piece SendPiece(Piece piece)
        {
            _sameArrived = false;
            Mediator.Instance.WaitFor(piece, sameArrived);

            while (!_sameArrived)
            {
                Thread.Sleep(100);
            }

            return _same;
        }

        protected void sameArrived(Piece piece)
        {
            _same = piece;
            _sameArrived = true;
        }
    }
}

Piece (entity)

namespace MyService
{
    [DataContract]
    public class Piece
    {
        [DataMember]
        public long ID { get; set; }

        [DataMember]
        public string SameIdentifier { get; set; }
    }
}

Mediator

namespace MyService
{
    public sealed class Mediator
    {
        private static Mediator _instance;

        private static object syncRoot = new Object();

        private List<Tuple<Piece, Action<Piece>>> _waitsFor;

        private Mediator()
        {
            _waitsFor = new List<Tuple<Piece, Action<Piece>>>();
        }

        public static Mediator Instance
        {
            get
            {
                if (_instance == null)
                {
                    lock (syncRoot)
                    {
                        _instance = new Mediator();
                    }
                }

                return _instance;
            }
        }

        public void WaitFor(Piece piece, Action<Piece> callback)
        {
            lock (_waitsFor)
            {
                var waiter = _waitsFor.Where(i => i.Item1.SameIdentifier == piece.SameIdentifier).FirstOrDefault();

                if (waiter != null)
                {
                    _waitsFor.Remove(waiter);
                    waiter.Item2(piece);
                    callback(waiter.Item1);
                }
                else
                {
                    _waitsFor.Add(new Tuple<Piece, Action<Piece>>(piece, callback));
                }
            }
        }
    }
}

And the client side code

    namespace MyClient
    {
        class Program
        {
            static void Main(string[] args)
            {
                Client c1 = new Client(new Piece()
                {
                    ID = 1,
                    SameIdentifier = "customIdentifier"
                });

                Client c2 = new Client(new Piece()
                {
                    ID = 2,
                    SameIdentifier = "customIdentifier"
                });

                c1.SendPiece();
                Console.WriteLine("Press return to send the second piece");
                Console.ReadLine();
                c2.SendPiece();
                Console.ReadLine();
            }
        }

        class Client
        {
            protected Piece _piece;

            protected Service1Client _service;

            public Client(Piece piece)
            {
                _piece = piece;
                _service = new Service1Client();
            }

            public void SendPiece()
            {
                Console.WriteLine("{0} send begins", _piece.ID);
                _service.BeginSendPiece(_piece, new AsyncCallback(sendPieceCallback), null);
            }

            protected void sendPieceCallback(IAsyncResult result)
            {
                Piece returnedPiece = _service.EndSendPiece(result);
                Console.WriteLine("{0} send completed, returned: {1}", _piece.ID, returnedPiece.ID);
            }
        }
    }

So is it a good idea to wait for another WCF call (which may or may not be invoked, so in a real example it would be more complex), and process them together with cross threading communication? Or not and I should look for another solution?

Thanks in advance,

negra


If you want to extend your application without changing any existing code, you can use MEF that is Microsoft Extensibility Framework.

For using MEF with silverlight see: http://development-guides.silverbaylabs.org/Video/Silverlight-MEF

I would not wait for 2 WCF calls from Silverlight, for the following reasons:

  • You are making your code more complex and less maintainable
  • You are storing business knowledge, that two services should be called together, in the client

I would call a single service that aggreagated the two services.


It doesn't feel like a great idea to me, to be honest. I think it would be neater if you could package up both "partial" requests in a single "full" request, and wait for that. Unfortunately I don't know the best way of doing that within WCF. It's possible that there's a generalized mechanism for this, but I don't know about it. Basically you'd need some loosely typed service layer where you could represent a generalized request and a generalized response, routing the requests appropriately in the server. You could then represent a collection of requests and responses easily.

That's the approach I'd look at, personally - but I don't know how neatly it will turn out in WCF.

0

精彩评论

暂无评论...
验证码 换一张
取 消