Using SignalR (.NET Client)

Asked 2 years ago, Updated 2 years ago, 38 views

US>We created a program that uses SignalR (.NET Client) to notify events and retrieve data.
I was able to do what I wanted to do, but I couldn't write the process very well.
Is it possible to write processing smarter considering multi-threaded problems and missing disposables?
If you use Rx, you may be able to write more beautifully, but I couldn't imagine how to write it with Rx by myself.

If you have any ideas, please help me.

■What you want to do

  • The server notifies the client of the occurrence of data at regular intervals.
  • Keep data in queue

  • When the client receives an event of data generation, it retrieves data from the server.

  • Retrieves data not only from events of data generation, but also immediately after connection.
  • Call data retrieval continuously until the server queue is empty
  • If the client fails to connect, try reconnecting after a certain period of time.
  • The client can start/stop at any time.

Keep data in queue.

When a client receives an event of data generation, the client retrieves data from the server.

■ Common Client/Server Source

public class data
{
    public int Id {get;set;}
    public string text {get;set;}
}

■ Server side source

//Hub
[HubName("test")]
public class TestHub —Hub
{
    // data acquisition processing
    public Data Query()
    {
        return Service.Deque();
    }
}

// notifications and queue management, pre-starting
public static class service
{
    private static readonly ConcurrentQueue<Data>queue=new ConcurrentQueue<Data>();

    private static int nextId = 1;

    private static timer;

    // Queue data and notify clients at regular intervals
    public static void Start (IHubConnectionContext<dynamic>clients)
    {
        timer=new Timer(state=>
        {
            nextId++;
            queue.Enqueue(newData{Id=nextId, Text=String.Format("Data-{0}", nextId)});

            clients.All.Update();
        }, null, 3000, 3000);
    }

    public static void Stop()
    {
        timer.Dispose();
    }

    public static Data Request()
    {
        Data;
        queue.TryFetchAndAction (out data);
        return data;
    }
}

■ Client side source (want to be smarter)

public class Client
{
    // Synchronous objects for hub operations
    private readonly object sync=new object();

    // Run state, used to reconnect when closed, and interrupt continuous processing of data retrieval
    private readonly ManualResetEvent running = new ManualResetEvent(false);

    // Timers for Reconnect
    private timer;

    private HubConnection hub;

    private IHubProxy proxy;

    private void Start()
    {
        if(hub!=null)
        {
            return;
        }

        // US>Transition to Run State
        running.Set();

        hub = new HubConnection("http://127.0.0.1:10080");
        hub.Closed+=()=>
        {
            // Clean up old timers
            vardisposable=Interlocked.Exchange(ref timer, null);
            if(disposable!=null)
            {
                disposable.Dispose();
            }

            if(running.WaitOne(0))
            {
                // Attempt to reconnect after a certain period of time
                Interlocked.Exchange (ref timer, new timer (state=>)
                {
                    lock (sync)
                    {
                        if(hub!=null)
                        {
                            ConnectHubAsync();
                        }
                    }
                }, null, 5000, 0);
            }
        };

        proxy=hub.CreateHubProxy("test");
        proxy.On("Update", FetchAndAction);

        ConnectHubAsync();
    }

    // Connections and data retrieval with connections
    private void ConnectHubAsync()
    {
        hub.Start().ContinueWith(t=>
        {
            if(hub.State==ConnectionState.Connected)
            {
                fetchAndAction();
            }
        });
    }

    // CONTINUOUS PROCESSING OF DATA ACQUISITION
    private async void fetchAndAction()
    {
        while(running.WaitOne(0))
        {
            Data;
            try
            {
                data = wait proxy.Invoke<Data>("Query");
            }
            catch (Exception ex)
            {
                // InvalidOperationException Occurs When HubConnection.Stop() During Invoke
                break;
            }

            // No more data
            if(data==null)
            {
                break;
            }

            // We're going to do this based on data.
        }
    }

    private void stop()
    {
        if(hub==null)
        {
            return;
        }

        // Move to Stopped State
        running.Reset();

        lock (sync)
        {
            vardisposable=Interlocked.Exchange(ref timer, null);
            if(disposable!=null)
            {
                disposable.Dispose();
            }

            hub.Stop();
            hub = null;
        }
    }
}

c# .net

2022-09-30 15:33

2 Answers

There are three types of synchronization mechanisms on the client side: lock(sync), Interlocked.Exchange, and ManualResetEvent.Unless the lock time is particularly critical, simply controlling it with lock(sync) will make it much simpler.

Also

var disposable=Interlocked.Exchange(ref timer, null);
if(disposable!=null)
{
    disposable.Dispose();
}

using statements

using(Interlocked.Exchange(ref timer, null)){}

can be written as


2022-09-30 15:33

Classes with some resource implement the IDisposable interface.
For example, in the Client class, Timer, ManualResetEvent, and so on are considered resources.
All of these features implement IDisposable.

When instances of the Client class are discarded, you must invoke the Dispose method for those objects.

If you don't need to inherit and use the Client class in the future, you'd better specify sold.
The following example implements an IDisposable pattern:

public sold class Client:IDisposable{
    private int dropped = 0;
    private void disposition (bool disposition) {
        if(Interlocked.CompareExchange(ref dispatched, 1,0)==0){
            // It frees up resources, i.e. timer.Dispose(); and so on.
            GC.SuppressFinalize (this);
        }
    }

    public voiddispose(){
        Dispose(true);
    }

    ~Client(){
        Dispose (false);
    }
}

The ~Client() part defines the destructor.
In this way, even if there is a case where Dispose is not called, GC will eventually call Dispose.
GC.SuppressFinalize(this) notifies GC that it is not necessary to call the destructor.
However, destructors are less likely to be recovered than objects that do not implement them.(Image to be put off)
Therefore, implementing the destructor recklessly is not recommended, but in this case, it seems to be a valid method.

If you want to derive a Client class, use the void disposition (bool disposing) method. Please specify virtual and override.

You can also use System.Threading.SemaphoreSlim as a locking mechanism for asynchronous patterns.
For more information, see Asynchronous: How do I lock a code that contains wait? (SemaphoreSlim Edition)

Please refer to the .


2022-09-30 15:33

If you have any answers or tips


© 2024 OneMinuteCode. All rights reserved.