简体   繁体   中英

Syncing between multiple instances of the same program

I have quite a complicated programming problem on my hands, so bear with me for a few minutes.

I decided i want to create a media player in WPF (C#) and i've run into a bit of a pickle.

I want my application to be single instance, so that when the user double clicks server files, the program would only run once and queue all files for playing.

I tried several ways of doing it, including Microsoft's single instance implementation, and nothing seemed to work, until i decided to create my own , as in i though of something and implemented it (this probably was on the internet somewhere as well, but it didn't show up)

Basically, i use a named mutex to prevent more than one instance from being opened, and to force the other instances to write their arguments to a file, and after that, the instance which created the mutex would read the file. Needless to say, this is very, very ineffective as far as performance goes, but anyway, here is my implementation of the Main() function. Note that this Main() is also written from scratch, as i didn't really like the one automatically generated by the VS2010.

static void Main(string[] args)
    {

            string[] arguments = new string[0];
            handler g = new handler();
            bool createdNew = false;
            Mutex lolpaca = new Mutex(true, "lolpacamaximumtrolololololol", out createdNew);
            if (createdNew)
            {

                if (args != null)
                {
                    var MainWindow = new MainWindow();
                    var app = new Application();
                    app.Run(MainWindow);
                    lolpaca.ReleaseMutex();
                    lolpaca.Dispose();
                }
                else
                {
                            Array.Resize(ref arguments, 1);
                            arguments[0] = args[0];
                            string line;
                    //nu mai arunca exceptii nenorocitule

                            while ((line = g.ReadArgs()) != null)
                            {
                                int old_size = arguments.Length;
                                Array.Resize(ref arguments, arguments.Length + 1);
                                arguments[old_size] = line;
                            }


                    var MainWindow = new MainWindow(arguments, arguments.Length);
                    var app = new Application();
                    app.Run(MainWindow);
                    lolpaca.ReleaseMutex();
                    lolpaca.Dispose();

                }
                if (File.Exists(path))
                {
                    File.Delete(path);
                }
            }

            else
            {
                Thread writer = new Thread(new ParameterizedThreadStart(g.WriteArg));
                writer.Start(args);
                writer.Join();

                 try
                {
                    g.WriteArg(args);
                }
                catch (IOException e)
                {
                    MediaPlayerFinal_GUI_new.ExceptionCatcher exp = new MediaPlayerFinal_GUI_new.ExceptionCatcher(e.Source);
                    exp.Show();
                }

            }

    }

I'm also using this class to attempt to sync between the threads

   public class handler
{  
    static string path = @"D:\playlist.txt";
    static FileStream fs = new FileStream(path, FileMode.Open, FileAccess.ReadWrite, FileShare.ReadWrite);
    string line;

    string arg;
    bool readerFlag = false;
    public string ReadArgs()
    {
        try
        {
            lock (fs)   // Enter synchronization block
            {
                if (!readerFlag)
                {            // Wait until writer  finishes
                    try
                    {
                        // Waits for the Monitor.Pulse in WriteArg
                        Monitor.Wait(fs);
                    }
                    catch (SynchronizationLockException)
                    {

                    }
                    catch (ThreadInterruptedException)
                    {

                    }
                }


                TextReader tr = new StreamReader(fs);
                while ((line = tr.ReadLine()) != null)
                {
                    arg = line;
                }
                tr.Close();
                tr.Dispose();

            }

          /*  fs.Close();
            fs.Dispose();*/
            readerFlag = false;
            Monitor.Pulse(fs);
            return arg;
        }
        catch (IOException e)
        {
            MediaPlayerFinal_GUI_new.ExceptionCatcher exp = new MediaPlayerFinal_GUI_new.ExceptionCatcher(e.Source);
            exp.Show();
            return null;
        }
    }
    public void WriteArg(object args)
    {
        lock (fs)
        {
            try
            {
                if (readerFlag)
                {
                    try
                    {
                        Monitor.Wait(fs);   // Wait for the Monitor.Pulse in ReadArgs
                    }
                    catch (SynchronizationLockException)
                    {

                    }
                    catch (ThreadInterruptedException)
                    {

                    }
                }
                arg = Convert.ToString(args);
                //   FileStream fs = new FileStream(path, FileMode.Append, FileAccess.Write, FileShare.Read);                
                TextWriter tw = new StreamWriter(fs);
                tw.WriteLine(args);
                tw.Close();
                tw.Dispose();


            }
            catch (IOException e)
            {
                MediaPlayerFinal_GUI_new.ExceptionCatcher exp = new MediaPlayerFinal_GUI_new.ExceptionCatcher(e.Source);
                exp.Show();
            }
        }
       /* fs.Close();
        fs.Dispose();*/
        readerFlag = true;
        Monitor.Pulse(fs);
    }

}

Now, basically, for each double clicked file, one instance of the Main() function is created by Windows. The first instance has control over the mutex and proceeds to doing whatever it wants to do. The other instances must write their argument to the file.

Now, the problem: Apparently, the threads (all of them) do no sync properly, and sometimes i get IO exceptions. I have no clue where exactly these exceptions are thrown, because the try-catch blocks seem to do exactly nothing. In fact, I believe this is a little deeper than try-catch would work on.

So, how do i sync all the threads that spawn when the user double clicks a lot of files? This implementation works ok with up to 3 double clicked files, and sometimes (note, sometimes it works, other times it doesn't) with more than 3 files (tested with up to 9). Nothing i found so far on the internet accounts for several instances of the same application running independently.

It would be great if you could give me an example:)

Thank you.

The best way to talk between two instances of the same application is use IPC. Bellow see example of class that can be used to help with single instance:

    /// <summary>
        /// Enforces single instance for an application.
        /// </summary>
        public class SingleInstance : IDisposable
        {
            #region Fields

            /// <summary>
            /// The synchronization context.
            /// </summary>
            private readonly SynchronizationContext synchronizationContext;

            /// <summary>
            /// The disposed.
            /// </summary>
            private bool disposed;

            /// <summary>
            /// The identifier.
            /// </summary>
            private Guid identifier = Guid.Empty;

            /// <summary>
            /// The mutex.
            /// </summary>
            private Mutex mutex;

            #endregion

            #region Constructors and Destructors

            /// <summary>
            /// Initializes a new instance of the <see cref="SingleInstance"/> class.
            /// </summary>
            /// <param name="identifier">
            /// An identifier unique to this application.
            /// </param>
            /// <param name="args">
            /// The command line arguments.
            /// </param>
            public SingleInstance(Guid identifier, IEnumerable<string> args)
            {
                this.identifier = identifier;

                bool ownsMutex;
                this.mutex = new Mutex(true, identifier.ToString(), out ownsMutex);

                this.synchronizationContext = SynchronizationContext.Current;

                this.FirstInstance = ownsMutex;

                if (this.FirstInstance)
                {
                    this.ListenAsync();
                }
                else
                {
                    this.NotifyFirstInstance(args);
                }
            }

            /// <summary>
            /// Initializes a new instance of the <see cref="SingleInstance"/> class.
            /// </summary>
            /// <param name="identifier">
            /// An identifier unique to this application.
            /// </param>
            public SingleInstance(Guid identifier)
                : this(identifier, null)
            {
            }

            #endregion

            #region Public Events

            /// <summary>
            /// Event raised when arguments are received from successive instances.
            /// </summary>
            public event EventHandler<OtherInstanceCreatedEventArgs> OtherInstanceCreated;

            #endregion

            #region Public Properties

            /// <summary>
            /// Gets a value indicating whether this is the first instance of this application.
            /// </summary>
            public bool FirstInstance { get; private set; }

            #endregion

            #region Implemented Interfaces

            #region IDisposable

            /// <summary>
            /// The dispose.
            /// </summary>
            public void Dispose()
            {
                this.Dispose(true);
                GC.SuppressFinalize(this);
            }

            #endregion

            #endregion

            #region Methods

            /// <summary>
            /// Clean up any resources being used.
            /// </summary>
            /// <param name="disposing">
            /// True if managed resources should be disposed; otherwise, false.
            /// </param>
            protected virtual void Dispose(bool disposing)
            {
                if (this.disposed)
                {
                    return;
                }

                if (disposing)
                {
                    if (this.mutex != null && this.FirstInstance)
                    {
                        this.mutex.WaitOne();
                        this.mutex.ReleaseMutex();
                        this.mutex = null;
                    }
                }

                this.disposed = true;
            }

            /// <summary>
            /// Fires the OtherInstanceCreated event.
            /// </summary>
            /// <param name="arguments">
            /// The arguments to pass with the <see cref="OtherInstanceCreatedEventArgs"/> class.
            /// </param>
            protected virtual void OnOtherInstanceCreated(OtherInstanceCreatedEventArgs arguments)
            {
                EventHandler<OtherInstanceCreatedEventArgs> handler = this.OtherInstanceCreated;

                if (handler != null)
                {
                    handler(this, arguments);
                }
            }

            /// <summary>
            /// Listens for arguments on a named pipe.
            /// </summary>
            private void Listen()
            {
                try
                {
                    using (var server = new NamedPipeServerStream(this.identifier.ToString()))
                    {
                        using (var reader = new StreamReader(server))
                        {
                            server.WaitForConnection();
                            var arguments = new List<string>();

                            while (server.IsConnected)
                            {
                                arguments.Add(reader.ReadLine());
                            }

                            this.synchronizationContext.Post(o => this.OnOtherInstanceCreated(new OtherInstanceCreatedEventArgs(arguments)), null);                        
                        }
                    }

                    // start listening again.
                    this.Listen();
                }
                catch (IOException)
                {
                    // Pipe was broken, listen again.
                    this.Listen();
                }          
            }

            /// <summary>
            /// Listens for arguments being passed from successive instances of the applicaiton.
            /// </summary>
            private void ListenAsync()
            {
                Task.Factory.StartNew(this.Listen, TaskCreationOptions.LongRunning);
            }

            /// <summary>
            /// Passes the given arguments to the first running instance of the application.
            /// </summary>
            /// <param name="arguments">
            /// The arguments to pass.
            /// </param>
            private void NotifyFirstInstance(IEnumerable<string> arguments)
            {
                try
                {
                    using (var client = new NamedPipeClientStream(this.identifier.ToString()))
                    {
                        using (var writer = new StreamWriter(client))
                        {
                            client.Connect(200);

                            if (arguments != null)
                            {
                                foreach (string argument in arguments)
                                {
                                    writer.WriteLine(argument);
                                }
                            }
                        }
                    }
                }
                catch (TimeoutException)
                {
                    // Couldn't connect to server
                }
                catch (IOException)
                {
                    // Pipe was broken
                }
            }



 #endregion
    }

/// <summary>
/// Holds a list of arguments given to an application at startup.
/// </summary>
public class OtherInstanceCreatedEventArgs : EventArgs
{
    #region Constructors and Destructors

    /// <summary>
    /// Initializes a new instance of the <see cref="OtherInstanceCreatedEventArgs"/> class.
    /// </summary>
    /// <param name="args">
    /// The command line arguments.
    /// </param>
    public OtherInstanceCreatedEventArgs(IEnumerable<string> args)
    {
        this.Args = args;
    }

    #endregion

    #region Public Properties

    /// <summary>
    /// Gets the startup arguments.
    /// </summary>
    public IEnumerable<string> Args { get; private set; }

    #endregion
}

Then in your main class you can create instance of of class that will stay until aplication is running. You can check if other instance is created by FirstInstance property, And get notified of other instance created by OtherInstanceCreated event.

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