3
votes

Multi thread et asynchrone en même temps

J'ai le code suivant:

myObject object1 = null;
Thread obj1Thread = new Thread(async () => { object1 = await _myService.GetMethod(variable1, variable2); });
obj1Thread.Start();
obj1Thread.Join();


myObject object2 = null;
Thread obj2Thread = new Thread(async () => { object2 = await _myService.GetMethod2(variable3, variable4); });
obj2Thread.Start();
obj2Thread.Join();

Pour autant que je sache, ce code créera 2 nouveaux threads, exécutera les méthodes spécifiées, mettra en pause le thread principal jusqu'à ce que ces deux threads soient terminés, et puis continuez l'exécution.

En supposant que ce que je dis est correct, tout va bien jusqu'à présent.

Ensuite, je veux essayer ceci:

myObject object1 = null;
Thread obj1Thread = new Thread(() => { object1 = _myService.GetMethod(variable1, variable2); });
obj1Thread.Start();
obj1Thread.Join();


myObject object2 = null;
Thread obj2Thread = new Thread(() => { object2 = _myService.GetMethod2(variable3, variable4); });
obj2Thread.Start();
obj2Thread.Join();

En gros, ajouter async et wait à chaque thread.

Le compilateur accepte ce changement et il semble s'exécuter localement, mais ce code est-il correct, et est-il susceptible de me causer des problèmes plus tard, par exemple, les threads seront-ils confus, n'attendront pas, mélangeront les résultats, etc.

J'ai une assez bonne compréhension de l'async et une compréhension de base du multi threading, et je ne vois aucune raison pour laquelle cela ne fonctionne pas.

Le code s'exécute localement, mais mon inquiétude est que sous une forte charge sur le serveur, des problèmes peuvent apparaître qui n'étaient pas présents dans une version locale ....


10 commentaires

Pourquoi avez-vous besoin des fils? Async n'est-il pas suffisant?


@tkausl c'est une API de chargement de page et de nombreuses actions sont en cours à la fois. Si je les fais la queue, ils prendront plus de 3 secondes. Si je les lance simultanément, cela prendra moins d'une demi-seconde. J'essaie de rendre mon serveur plus efficace en utilisant async.


Votre exemple de départ semble tout simplement faux: démarrez un nouveau thread pour exécuter du code, puis mettez immédiatement en pause le thread d'origine jusqu'à ce que le nouveau thread se termine. Puis rincez et répétez avec le deuxième fil. Cela a exactement le même comportement que si vous venez d'écrire directement les deux appels GetMethod sans jamais toucher aux threads.


@Damien_The_Unbeliever pouvez-vous expliquer pourquoi? Autant que je sache, les nouveaux threads exécuteront leur code jusqu'à ce qu'ils atteignent leurs E / S, à quel point ils libéreront leur thread jusqu'à ce que les E / S soient terminées, puis termineront leur tâche. Le scénario que vous décrivez est très différent de celui ...


Les threads ont un sens pour l'activité liée au processeur, en particulier lorsque vous essayez de fournir un certain degré «d'équité». Si votre charge de travail est liée aux E / S (réseau, disque, base de données), le threading peut ne pas aider et aggraver les choses. Ces types de charges de travail bénéficient de la structuration asynchrone.


Rejoindre : "Bloque le thread appelant jusqu'à ce que le thread représenté par cette instance se termine". Vous ne passez à la création de votre deuxième fil qu'après Rejoindre lors des premiers retours.


Pouvez-vous décrire la charge de travail?


@Damien_The_Unbeliever si j'exécute 3 méthodes sans threading, elles prennent en moyenne 0,85 seconde. Si je les lance avec un filetage, ils prennent en moyenne 0,45 seconde - tous deux avec 10 appels et les mêmes données. Comment cela peut-il être expliqué si les appels ne sont pas exécutés sur de nouveaux threads?


@Damien_The_Unbeliever stackoverflow.com/questions / 54181246 /…


Quel est l'intérêt de démarrer un nouveau fil si vous bloquez toujours le fil appelant en appelant Join immédiatement après? Votre code ne s'exécute pas en parallèle.


3 Réponses :


0
votes

Votre code ci-dessus ne profite pas du threading, il bloque le premier thread jusqu'à ce qu'il se termine puis démarre le second

obj1Thread.Join (); demande à votre thread principal d'attendre jusqu'à obj1Thread quitte avant de continuer. Cela signifie qu'il lancera obj1Thread puis attendra qu'il se termine, ce qui signifie que vous:

  1. créer le fil 1
  2. Exécuter le fil 1
  3. Quitter le fil 1
  4. créer le fil de discussion 2
  5. Exécutez le fil de discussion 2
  6. Quitter le fil de discussion 2

Vous voulez faire:

myObject object1 = null;
Thread obj1Thread = new Thread(async () => { object1 = await _myService.GetMethod(variable1, variable2); });
obj1Thread.Start();
myObject object2 = null;
Thread obj2Thread = new Thread(async () => { object2 = await _myService.GetMethod2(variable3, variable4); });
obj2Thread.Start();
obj1Thread.Join();
obj2Thread.Join();


3 commentaires

Vous avez raison, mais de manière générique, il est préférable de les sous-threader car il y a presque toujours une certaine quantité de traitement que vous pouvez faire avec le thread principal. Ce n'est tout simplement pas montré dans ce cas particulier.


Vous créez essentiellement 2 nouveaux threads pour les suspendre presque immédiatement.


Dépend de la durée de chaque service. Dans le cas d'une seule lecture d'E / S, c'est un peu trop mais s'il y a un traitement impliqué après la lecture asynchrone, cela en vaut la peine. Encore une fois, cela parle d'un point de vue général concernant la question d'OP.



2
votes

Votre code peut être parallèle et attendu de manière asynchrone avec une seule ligne:

Task<int> task1 = _myService.GetMethod(variable1, variable2);
Task<string> task2 = _myService.GetMethod2(variable3, variable4);
// Both tasks started.

int a = await task1; // Wait for first task
string b = await task2; // If this already finished, it will just return the result

C'est tout ce dont vous avez besoin. Pas de fils. Pas d'adhésion. Si ces méthodes sont vraiment des entrées / sorties, il n'y aura pas de thread.

Comme toujours, doit lire: https://blog.stephencleary.com/2013/11/there-is-no-thread.html

Si votre Les méthodes renvoient des résultats différents et doivent être affectées à des variables, alors vous pouvez le faire:

await Task.WhenAll(_myService.GetMethod(variable1, variable2), _myService.GetMethod2(variable3, variable4)). 


2 commentaires

que diriez-vous quand mon code attribue une valeur à une variable dans la fonction lambda?


@Alex Ensuite, vous pouvez démarrer les deux méthodes, afin qu'elles s'exécutent en parallèle, puis attendent chacune d'elles et attribuent les variables appropriées.



3
votes

Le compilateur accepte ce changement et il semble s'exécuter localement, mais ce code est-il correct, et est-il susceptible de me causer des problèmes plus tard, par exemple les threads seront-ils confus, échoueront-ils à attendre, mélangeront les résultats etc.

J'ai une assez bonne compréhension de l'async et une compréhension de base du multi threading, et je ne vois aucune raison pour laquelle cela ne fonctionnerait pas.

Oui, ce code vous posera des problèmes. Les threads n'attendent pas comme prévu. Vous transmettez un async void lambda a > au constructeur Thread , et ce thread se fermera dès qu'il atteindra le wait dans ce lambda, avant em > il définit la variable object1 / object2 . Il est donc tout à fait possible que ces variables restent nulles après le Join.

La bonne solution, comme FCin publié , consiste à utiliser la concurrence asynchrone. (J'évite le terme «parallèle» ici pour réduire la confusion avec le type Parallèle et la bibliothèque parallèle de tâches). La concurrence asynchrone utilise Task.WhenAll:

// Start both methods concurrently
var task1 = _myService.GetMethod(variable1, variable2);
var task2 = _myService.GetMethod2(variable3, variable4);

// (Asynchronously) wait for them both to complete.
await Task.WhenAll(task1, task2);

// Retrieve results.
myObject object1 = await task1;
myObject object2 = await task2;

0 commentaires