6
votes

C # Filetage / Serrure Confusion

J'ai le code suivant:

var items = new List<string> {"1", "2", "3"}; // 200 items
foreach(var item in items) {
  ThreadPool.QueueUserWorkItem((DoWork), item);
}

private void DoWork(object obj)
{
  lock(this)
  {
    using(var sw = File.AppendText(@"C:\somepath.txt")
    {
      sw.WriteLine(obj);
    }
  }
}


0 commentaires

3 Réponses :


9
votes

La note suivante de Documentation MSDN sur threadpool < / code> dit tout:

Les fils dans la piscine de thread gérée sont des filets de fond. C'est-à-dire que leurs propriétés isbackground sont vraies. Cela signifie qu'un thread de threadpool ne conserve pas une application en cours d'exécution après que tous les fils de premier plan soient sorties .

Votre application sort simplement (en atteignant la fin de principal ) avant la fin de vos threads en marche.


3 commentaires

Une façon de résoudre ce problème est d'utiliser Interlocked.incrèce pour garder une trace de combien de fois Dowork est appelé, définissant un événement de réinitialisation automatique lorsqu'il frappe le numéro magique. Le fil principal peut attendre sur cet événement au lieu de la fermeture.


N'a-t-il pas un moyen de dire le fil principal d'attendre que tous les fils de threadpool soient terminés avec le travail?


Le commentaire de Steven explique comment faire cela.



2
votes

Ceci est une version simple de ce que je faisais allusion. Il utilise un seul événement et ne sondre ni ne tourne pas, et il est écrit de manière à être réutilisable et permettant de permettre de multiples ensembles de travail en même temps. Les expressions Lambda pourraient être prises en compte, si cela est plus pratique pour le débogage.

class Program
{
    static void Main(string[] args)
    {
        var items = new string[] { "1", "2", "3", "300" };
        using (var outfile = File.AppendText("file.txt"))
        {
            using (var ws = new WorkSet<string>(x =>
                    { lock (outfile) outfile.WriteLine(x); }))
                foreach (var item in items)
                    ws.Process(item);
        }
    }

    public class WorkSet<T> : IDisposable
    {
        #region Interface

        public WorkSet(Action<T> action)
        { _action = action; }

        public void Process(T item)
        {
            Interlocked.Increment(ref _workItems);
            ThreadPool.QueueUserWorkItem(o =>
                    { try { _action((T)o); } finally { Done(); } }, item);
        }

        #endregion
        #region Advanced
        public bool Done()
        {
            if (Interlocked.Decrement(ref _workItems) != 0)
                return false;

            _finished.Set();
            return true;
        }

        public ManualResetEvent Finished
        { get { return _finished; } }

        #endregion
        #region IDisposable

        public void Dispose()
        {
            Done();
            _finished.WaitOne();
        }

        #endregion
        #region Fields

        readonly Action<T> _action;
        readonly ManualResetEvent _finished = new ManualResetEvent(false);
        int _workItems = 1;

        #endregion
    }
}


0 commentaires

0
votes

Que diriez-vous de courte et de doux? XXX


8 commentaires

Droite, c'est l'algorithme que j'ai affiché, en désactivant toute réutilisabilité. Cela nécessite également que vous sachiez combien d'articles de travail il y aura, à l'avance, alors que la solution que j'ai proposée ne le fait pas. Cependant, il réfute la suggestion de Spencer selon laquelle cet algorithme est lui-même long.


C'est tout. Raison que j'ai posté, c'est que la question de la question n'est pas encore grande sur la multi-threading et cette version courte résout son problème tout en démontrant clairement une méthode de synchronisation de threads rudimentaire.


Je comprends votre motivation, mais je ne suis pas sauvage du fait que vous utilisez un objet générique pour verrouiller au lieu de la FileStream lui-même.


Je ne suis pas sauvage que vous utilisez un objet encombré localement pour se verrouiller non plus. Peu importe que cela entraîne un résultat prévisible.


Ce n'est pas local; Le filtream est conçu dans le délégué anonyme par une fermeture. Étant donné que l'intérieur à l'aide du bloc ne se termine pas tant que toutes les filetages ne le font, la FileStream dans l'extérieur à l'aide de bloc est garantie pour être vivante tout le temps.


Si vous essayez d'accéder à ce fichier ailleurs (ou même la même méthode) avant l'achèvement et l'élimination, vous êtes susceptible d'avoir une exception au lieu d'attendre la fin du fil de l'écrivain. Ce verrou n'est valable que pour l'accès au sein du délégué et sera globalement efficace que si vous pouvez garantir un accès synchrone à la méthode principale (ARG) (que vous pouviez bien sûr, mais ce n'est pas le point). Alors oui, c'est local.


Je ne suis pas sûr de comprendre. Ce n'est pas local dans lequel il est partagé dans toutes les instances de la méthode anonyme. Si quelqu'un d'autre essaie d'ouvrir le fichier pour écrire ou ajouter de l'intérieur mon processus ou non), je m'attendrais à ce qu'elle échoue, car mon hfIle a un accès exclusif. L'objectif de la serrure est d'empêcher l'accès simultané au sein de ma demande, parmi ces threads de travailleur, de ne pas agir en tant que méthode de synchronisation croisée. Je suppose qu'un thread de travailleur pourrait passer l'instance à une méthode qui la met en caches et abonne-t-elle un nouveau fil qui n'honore pas la serrure, mais donc quoi?


Donc, rien vraiment, je me faisais simplement réagir à votre commentaire et soulignez que la mise en œuvre dépendra de la demande.