3

以下のようにネストされたループ内で非同期関数を呼び出しました

var queue = new Queue<ExchangeEmailInformation>(mailInformation);
var currentQueue = queue.ToList();
foreach (var exchangeEmailInformation in currentQueue)
{
    ExchangeEmailInformation information = exchangeEmailInformation;
    foreach (var queueList in exchangeEmailInformation.Attachment)
    {
        Attachment attachment = queueList;
        information.FileName = attachment.Name;
        var emailId = information.Sender.Split('@');
        information.UserAlias = emailId[0];
        information.FileSize = attachment.Size;
        AddAttachmentAsync(information);

    }

}

private static void AddAttachmentAsync(ExchangeEmailInformation information)
{
    System.Threading.Tasks.Task.Factory.StartNew(
        () =>
        AddAttachment(information.UserAlias, information.EngagementName,
                        information.DocumentTransferId.ToString(), information.FileName,
                        information.FileSize.ToString(), information.ActivityName)).ContinueWith(
                            task => OnComplete(task, information), TaskContinuationOptions.None);
}

static void AddAttachment(string userAlias, string engagementName, string documentTranferId, string fileName, string fileSize,string activityName)
{
    Console.Writeline(fileName);

}

In the exchange information collection has one record. In these collection there is another property called Attachment which type is AttachmentCollection which contain two attachments. After calling the method AddAttachmentAsync like above asynchronously the 

印刷された結果は

  • SecondAttachment.txt
  • SecondAttachment.txt。

2番目の添付ファイルのみを表示しています(誤った結果)。

次に、以下のように同期的に実行しようとします。

private static void AddAttachmentAsync(ExchangeEmailInformation information)
{
    AddAttachment(information.UserAlias, information.EngagementName,
                    information.DocumentTransferId.ToString(), information.FileName,
                    information.FileSize.ToString(), information.ActivityName);


}

結果は

  • FirstAttachment.txt

  • SecondAttachment.txt

思い通りに正しい結果を表示

これらの問題を解決するにはどうすればよいですか?

4

5 に答える 5

5

informationネストされたループの外で宣言された参照型オブジェクトです。このオブジェクトをAddAttachmentAsyncメソッドに渡していますが、完了するのを待つ前 (または の処理を​​開始する前)に、次の反復でTask既に変更しています。information

information非同期メソッドに送信する前に、 のコピーを作成する必要があります。

マークが指摘するように編集します。これは、同じオブジェクトへの新しい参照だけでなく、値がコピーされた新しいオブジェクトである必要があります。

于 2013-01-10T08:58:56.323 に答える
2

これは、単一の外側ループ反復の内側ループ反復ごとに同じExchangeEmailInformationインスタンスを使用しているためだと思います。前の呼び出しがその値を読み取る機会を得る前に、次の非同期呼び出しのためにそのインスタンスのプロパティを更新します。informationforeachforeach

非同期の場合、イベントの順序は次のとおりです。

  1. コールinformation1 の入力
  2. コールinformation2 の入力
  3. コール 1 を実行
  4. コール 2 の実行

そのため、呼び出し 1 が実行されるまでに、information呼び出し 2 のデータが既に含まれています。同期の場合、これは発生しません。呼び出し 1 の実行が完了するまで、ループは続行できません。

これを修正する最善の方法は、変更 informationを停止し、以前に変更された 3 つのフィールドを個別のパラメーターとして渡すことだと思います。UserAlias(実際には一度だけ更新する必要があるように見えるので、個別に渡す必要はありません。また、queueListこれを行う場合、のコピーを取る必要はありません。)

ExchangeEmailInformation information = exchangeEmailInformation;
var emailId = information.Sender.Split('@');
information.UserAlias = emailId[0];

foreach (var queueList in exchangeEmailInformation.Attachment)
{
    AddAttachmentAsync(information, queueList.Name, queueList.Size);
}

// and modify AddAttachmentAsync to use these two parameters too

別の方法は、あなたが行っていたように のコピーを取得し、そのコピーqueueListの両方を渡し、必要に応じて 2 つのそれぞれからパラメーターをプルすることです。information AddAttachmentAsync

ExchangeEmailInformation information = exchangeEmailInformation;
var emailId = information.Sender.Split('@');
information.UserAlias = emailId[0];

foreach (var queueList in exchangeEmailInformation.Attachment)
{
    var attachment = queueList;
    AddAttachmentAsync(information, attachment);
}

// and modify AddAttachmentAsync to pull properties from the right parameter.
于 2013-01-10T08:59:23.977 に答える
2

の閉鎖を変更しましたinformation

于 2013-01-10T08:57:18.377 に答える
1

2 番目の foreach() の 1 行目で、'information' のコピーを作成し、そのコピーを AddAttachmentAsync に渡す必要があります。(つまり、オブジェクト参照のコピーだけでなく、すべてのデータのコピーです。)

何が起こっているかというと、AddAttachmentAsync() に渡す「情報」オブジェクトが、AddAttachmentAsync() が戻る前に変更されているということです。

一般に、マルチスレッドに使用される ExchangeEmailInformation などのクラスを設計するときは、それらを不変にする必要があります。そうすると、このような特定のことが起こることはありません。(私の意見では、すべての POD (「単純な古いデータ」) クラスを不変にする必要があります。)

于 2013-01-10T09:00:35.010 に答える
0

各スレッドのインスタンスを作成する必要があることを正しく評価する他の回答に加えて、information別の提案をします。呼び出しを作成したことに気付きましたがAddAttachment、現在は単に作成しているだけConsole.Writeですが、最終的にはリストにアクセスして、結果のインスタンスをこのリストに追加することになると思います。これにより、共有リソースにアクセスするという不必要な状況が作成されます。

各スレッドがその結果をリストに追加する責任を負う代わりに、マスター スレッドが参照型プロパティを持つオブジェクトのインスタンスを作成し、これをスレッドに渡す方がはるかに簡単です。このように、スレッドには結果を保存する場所が既にあり、共有リソースではないため、同期について心配する必要はありません。

これは非常に基本的な例です。明らかに、必要に応じて少し変更する必要があります。

class Result
{
   public object Data { get; set; }
}

List<Work> WorkToDo = // Some population call
List<Result> Results = new List<Result>();
foreach (Work Item in WorkToDo)
{
    Result Result = new Result();
    Results.Add(Result);
    System.Threading.Tasks.Task.Factory.StartNew(
        () => { Result.Data = "Hello World."; });
}

繰り返しますが、すべての結果を何らかのリストまたは配列にコンパイルしたいからといって、リストへのアクセスを共有する必要があるわけではありません。親スレッドにすべてを処理させると、アルゴリズムが大幅に簡素化されます。

于 2013-01-10T09:12:52.853 に答える