C#: Как препятствовать тому, чтобы два экземпляра приложения делали то же самое одновременно?

Если у Вас есть два потока в рамках приложения, и Вы не хотите, чтобы они выполнили определенную часть кода одновременно, можно просто поместить блокировку вокруг части кода, как это:

lock (someObject) {
    // ... some code
}

Но как дела то же самое через отдельные процессы? Я думал, что это - то, для чего Вы используете "глобальное взаимное исключение", таким образом, я попробовал Mutex класс различными способами, но это, кажется, не выполняет мои требования, которые являются:

  • Если Вы - единственный экземпляр, разрешение и выполняете код.
  • Если Вы - второй экземпляр, ожидаете первого, чтобы закончиться, затем выполнить код.
  • Не выдавать исключения.

Проблемы я столкнулся:

  • Просто инстанцирование a Mutex объект в a using(){...} пункт, кажется, ничего не делает; эти два экземпляра все еще счастливо выполняются одновременно
  • Вызов .WaitOne() на Взаимоисключающих причинах первая инстанция для выполнения и второе для ожидания, но второе ожидает неограниченно долго, даже после первых вызовов .ReleaseMutex() и листы using(){} объем.
  • .WaitOne() выдает исключение, когда первый процесс выходит (System.Threading.AbandonedMutexException).

Как я решаю это? Решения, которые не включают Mutex очень приветствуются, тем более, что Mutex кажется, является определенным для Windows.

7
задан Timwi 24 February 2010 в 21:57
поделиться

6 ответов

Извините за ответ от первого лица, но хотя я не использовал JS.Class, с точки зрения разработчика Java/Javascript, он выглядит очень хорошо. Наша команда работает над проектом Java с большей частью нашего переднего плана, используя домашний JavaScript (и не небольшое количество его). Мы не используем JS рамок (jQuery не существовал, когда начинался наш проект), и мы даже внедрили наши собственные утилиты перетаскивания/сортировки, которые широко используют наследование.

Для удобства и для возможности использования наследования (и в результате большой эволюции) мы разработали методы, которые приходят почти к тем же узорам, что и JS.Class (но не так обширны).

Все, что я прочитал в документах о классах и модулях, наследовании и т.д., казалось очень естественным - на самом деле, похоже, мы могли бы заменить все наши методы создания классов (метод, который мы назвали objectLib.createClass ) на новый JS.Class без каких-либо других изменений.

В итоге мы также разработали собственные классы Set и другие утилиты массивов и объектов, которые JS.Class включает в свою основную функциональность.

Так что, хотя я не могу сказать, что я использовал JS.Class и мне это понравилось, я могу сказать, что как разработчик Javascript мне нужно было решить почти точные проблемы, которые JS.Class, по-видимому, решить очень хорошо.

-121--3181185-

Данные:: Структура:: Util имеет функцию unbless , которая сделает это для вас. Как указывает Эрик, JSON:: XS обычно не принимает благословенные ссылки (хотя я бы хотел, чтобы он просто проигнорировал это и занялся структурой данных). В этом случае вокруг него нет пути.

Но подумайте, почему вы думаете, что вам нужно это освободить. Ты делаешь это для одного из своих классов или для другого? Это звучит подозрительно как «Неправильная вещь.» Возможно, есть лучший способ.

У вас та же проблема, что и при разрыве инкапсуляции, потому что вы должны предположить, что знаете, какова внутренняя структура ссылки. Если вы собираетесь сделать это, вы можете просто игнорировать объектно-ориентированные вещи и получить доступ к структуре напрямую.

Если вы собираетесь сделать это для собственного класса, рассмотрите возможность предоставления метода для возврата структуры данных (которая не обязательно должна быть исходной структурой) вместо изменения объекта.

Вы упоминаете в последующем комментарии, что вы, возможно, делаете это, чтобы обойти некоторое поведение Template Toolkit. У меня была такая ситуация двумя способами в зависимости от ситуации:

  • Передайте только необходимые данные шаблону вместо целого объекта.
  • Добавьте методы к объекту, чтобы получить нужные данные в шаблоне.

Perl - это DWIM, но TT - это даже DWIMmier, что иногда прискорбно.


Вот быстрый взлом, где я определяю TO _ JSON в UNIVERSAL , так что он применяется ко всем объектам. Он создает глубокую копию, распаковывает ее и возвращает структуру данных.

#!perl
use v5.10;

sub UNIVERSAL::TO_JSON {
    my( $self ) = shift;

    use Storable qw(dclone);
    use Data::Structure::Util qw(unbless);

    my $clone = unbless( dclone( $self ) );

    $clone;
    }

my $data = bless {
    foo => bless( [], 'Local::Array' ),
    quack => bless( {
        map { $_ => bless [$_, $_**2], 'Local::Array' } 
            grep { is_prime } 1 .. 10
        }, 'Local::Hash' ),
    }, 'Local::Hash';

use JSON::XS;
my $jsonner = JSON::XS->new->pretty->convert_blessed(1);
say $jsonner->encode( $data );
-121--1156051-

У меня два приложения:

ConsoleApplication1.cs

using System;
using System.Threading;

namespace ConsoleApplication1
{
    class Program
    {
        static void Main(string[] args)
        {
            Mutex mutex = new Mutex(false, "AwesomeMutex");

            Console.WriteLine("ConsoleApplication1 created mutex, waiting . . .");

            mutex.WaitOne();

            Console.Write("Waiting for input. . .");
            Console.ReadKey(true);

            mutex.ReleaseMutex();
            Console.WriteLine("Disposed mutex");
        }
    }
}

ConsoleApplication2.cs

using System;
using System.Threading;

namespace ConsoleApplication2
{
    class Program
    {
        static void Main(string[] args)
        {
            Mutex mutex = new Mutex(false, "AwesomeMutex");
            Console.WriteLine("ConsoleApplication2 Created mutex");

            mutex.WaitOne();

            Console.WriteLine("ConsoleApplication2 got signalled");

            mutex.ReleaseMutex();
        }
    }
}

Запуск ConsoleApplication1, за которым следует ConsoleAplication2, отлично работает без ошибок. Если ваш код все еще бомбит, это ошибка с вашим кодом, а не класс Mutex.

5
ответ дан 6 December 2019 в 21:12
поделиться

Я успешно использовал Mutex именно для этой цели и могу подтвердить, что он работает, хотя и с некоторыми причудами.

У меня дома есть полный рабочий пример кода. Просто оставьте комментарий к этому ответу, если вы хотите, чтобы я добавил пример кода сегодня вечером.

ОБНОВЛЕНИЕ:

Вот урезанный код моего производственного приложения. Это консольное приложение, но тот же принцип должен применяться к любому типу приложения. Запустите с аргументом командной строки

--mutex 

, чтобы проверить логику мьютекса.

Обратите внимание, что в моем случае мьютекс действительно защищает большую часть процесса, но нет причин использовать его таким образом. Это как раз то, что было нужно в данном случае.

using System;
using System.IO;
using System.Collections.Generic;
using System.Text;
using System.Diagnostics;
using System.Threading;

namespace MyNameSpace
{
    class Program
    {
        // APP_GUID can be any unique string.  I just opted for a Guid.  This isn't my real one :-)
        const string APP_GUID = "1F5D24FA-7032-4A94-DA9B-F2B6240F45AC";

        static int Main(string[] args)
        {
            bool testMutex = false;
            if (args.Length > 0 && args[0].ToUpper() == "--MUTEX")
            {
                testMutex = true;
            }

            // Got variables, now only allow one to run at a time.

            int pid = System.Diagnostics.Process.GetCurrentProcess().Id;

            int rc = 0;

            Mutex mutex = null;
            bool obtainedMutex = false;
            int attempts = 0;
            int MAX_ATTEMPTS = 4;

            try
            {
                mutex = new Mutex(false, "Global\\" + APP_GUID);

                Console.WriteLine("PID " + pid + " request mutex.");

                while (!obtainedMutex && attempts < MAX_ATTEMPTS)
                {
                    try
                    {
                        if (!mutex.WaitOne(2000, false))
                        {
                            Console.WriteLine("PID " + pid + " could not obtain mutex.");
                            // Wait up to 2 seconds to get the mutex
                        }
                        else
                        {
                            obtainedMutex = true;
                        }
                    }
                    catch (AbandonedMutexException)
                    {
                        Console.WriteLine("PID " + pid + " mutex abandoned!");
                        mutex = new Mutex(false, "Global\\" + APP_GUID); // Try to re-create as owner
                    }

                    attempts++;
                }

                if (!obtainedMutex)
                {
                    Console.WriteLine("PID " + pid + " gave up on mutex.");
                    return 102;
                }


                Console.WriteLine("PID " + pid + " got mutex.");

                // This is just to test the mutex... keep one instance open until a key is pressed while
                // other instances attempt to acquire the mutex
                if (testMutex)
                {
                    Console.Write("ENTER to exit mutex test....");
                    Console.ReadKey();
                    return 103;
                }

                // Do useful work here

            }
            finally
            {
                if (mutex != null && obtainedMutex) mutex.ReleaseMutex();
                mutex.Close();
                mutex = null;
            }

            return rc;
        }
    }
}
4
ответ дан 6 December 2019 в 21:12
поделиться

Вы можете иметь два объекта для одного файла одновременно (один для чтения, один для записи):

def removeLine(filename, lineno):
    fro = open(filename, "rb")

    current_line = 0
    while current_line < lineno:
        fro.readline()
        current_line += 1

    seekpoint = fro.tell()
    frw = open(filename, "r+b")
    frw.seek(seekpoint, 0)

    # read the line we want to discard
    fro.readline()

    # now move the rest of the lines in the file 
    # one line back 
    chars = fro.readline()
    while chars:
        frw.writelines(chars)
        chars = fro.readline()

    fro.close()
    frw.truncate()
    frw.close()
-121--1626370-

Я не думаю, что вы можете сделать это автоматически и надежно. Например, как бы вы автоматически сказали, что Хуан Карлос Перес идет на P, а Ричард Милхус Никсон идет на N?

Вам придется использовать эвристику и словарь имен или что-то подобное, и это всегда провалится в некоторых случаях. Лучше всего сделать то, что говорит Джульетта: обработать данные, чтобы сгенерировать кандидатов для обзора, и разделить столбец имени на три (или четыре) столбца: FirstName, MiddleInitial, LastName.

-121--4859338-

Необходимо использовать именованный мьютекс. Существует отдельный конструктор, позволяющий определить именованный мьютекс .

4
ответ дан 6 December 2019 в 21:12
поделиться

Хотя я бы рекомендовал вам использовать Mutex и выяснить, не в вашем ли коде проблема, одной из очень сложных и хрупких альтернатив может быть использование "файлов блокировки".

По сути, вы создаете временный файл в файловой системе с именем, которое распознают оба процесса. Если файл существует, то блокировка установлена; другой процесс должен освободить все остальные блокировки, подождать и попытаться получить их снова. Если файл не существует, блокировка не установлена; процесс должен создать файл для завершения блокировки и продолжить обработку, удалив файл при освобождении блокировки.

Как я уже сказал, это очень сложный и хрупкий способ, но он не использует Mutex.

В качестве альтернативы используйте Mutex.

0
ответ дан 6 December 2019 в 21:12
поделиться

Если вы не хотите использовать Mutex, я бы сказал, что простым решением будет создание своего собственного, используя, скажем, существующий файл. Если файл существует, то не нужно начинать новый. Однако вам придется обрабатывать исключительные случаи, когда процесс завершается раньше времени и файл не очищается.

Возвращаясь на мгновение к мьютексу, хочу сказать, что это просто "объект" ОС. Вам действительно нужно иметь несколько таких объектов в каждой ОС, которую вы используете. Я уверен, что другие .net clr'ы позволят вам получить доступ к этим системным примитивам. Я бы просто обернул каждый из них с помощью определенной сборки, которая может быть разной на каждой платформе.

Надеюсь, это имеет смысл.

0
ответ дан 6 December 2019 в 21:12
поделиться

Возможно, я упрощаю проблему, но раньше я только что проверял имя процесса.

Вы можете просто использовать эту функцию, чтобы дождаться завершения другой, а затем запустить текущий процесс.

''' <summary>
''' Is this app already running as a standalone exe?
''' </summary>
''' <returns></returns>
''' <remarks>
''' NOTE: The .vshost executable runs the whole time Visual Studio is open, not just when debugging.
''' So for now we're only checking if it's running as a standalone.
''' </remarks>
public bool AlreadyRunning()
{
    const string VS_PROCESS_SUFFIX = ".vshost";

    //remove .vshost if present
    string processName = Process.GetCurrentProcess.ProcessName.Replace(VS_PROCESS_SUFFIX, string.Empty);
    
    int standaloneInstances = Process.GetProcessesByName(processName).Length;
    //Dim vsInstances As Integer = Process.GetProcessesByName(processName & VS_PROCESS_SUFFIX).Length
    
    //Return vsInstances + standaloneInstances > 1
    return standaloneInstances > 1;
}
-1
ответ дан 6 December 2019 в 21:12
поделиться
Другие вопросы по тегам:

Похожие вопросы: