2

でブロックしようとしてRequestHandler.ParseAll()await ConsumerTask;ますが、そこにブレークポイントを設定すると、常に最初に「Done ...」出力を取得し、次にParse2()NullReferenceExceptionで失敗します。(それは私の推測です:「_handlerスコープから外れたため、GCはクリーンアップを開始します」)

とにかく、なぜそれが起こるのか理解できません。

class MainClass
{
    public async void DoWork()
    {
        RequestHandler _handler = new RequestHandler();
        string[] mUrls;
        /* fill mUrls here with values */
        await Task.Run(() => _handler.ParseSpecific(mUrls));
        Console.WriteLine("Done...");
    }
}
static class Parser
{
    public static async Task<IEnumerable<string>> QueryWebPage(string url) { /*Query the url*/ }

    public static async Task Parse1(Query query) 
    { 
        Parallel.ForEach(/*Process data here*/);
    }

    public static async Task Parse2(Query query)
    {
        foreach(string line in query.WebPage)
            /* Here i get a NullReference exception because query.WebPage == null */
    }
}
sealed class RequestHandler
{
    private BlockingCollection<Query> Queue;
    private Task ConsumerTask = Task.Run(() => /* call consume() for each elem in the queue*/);

    private async void Consume(Query obj)
    {
        await (obj.BoolField ? Parser.Parse1(obj) : Parser.Parse2(obj));
    }

    public async void ParseSpecific(string[] urls)
    {
        foreach(string v in urls)
            Queue.Add(new Query(await QueryWebPage(v), BoolField: false));

        Queue.CompleteAdding();
        await ConsumerTask;
        await ParseAll(true);
    }

    private async Task ParseAll(bool onlySome)
    {
        ReInit();
        Parallel.ForEach(mCollection, v => Queue.Add(new Query(url, BoolField:false)));
        Queue.CompleteAdding();
        await ConsumerTask;
        /* Process stuff further */
    }
}
struct Query
{
    public readonly string[] WebPage;
    public readonly bool BoolField;
    public Query(uint e, IEnumerable<string> page, bool b) : this()
    {
        Webpage = page.ToArray();
        BoolField = b;
    }
}
4

1 に答える 1

6

CodesInChaosはコメントで問題を発見しました。これは、非同期メソッドが返されることに起因しvoidますが、これはほとんど絶対に行わないでください。つまり、それらを追跡する方法がないということです。

代わりに、非同期メソッドに返す実際の値がない場合は、それらを返すようにする必要がありますTask

何が起こっているのかというと、最初の実行がすぐに完了しないParseSpecificまで同期的に実行されているだけです。await QueryWebPage(v)その後、戻ってきます...タスクはここから始まりました:

await Task.Run(() => _handler.ParseSpecific(mUrls));

...すぐに完了し、「Done」が出力されます。

すべての非同期メソッドを返すようTaskにしたら、それらを待つことができます。また、まったく必要ありませんTask.Run。だからあなたは持っているでしょう:

public async void DoWork()
{
    RequestHandler _handler = new RequestHandler();
    string[] mUrls;
    await _handler.ParseSpecific(mUrls);
    Console.WriteLine("Done...");
}

..。

public async TaskParseSpecific(string[] urls)
{
    foreach(string v in urls)
    {
        // Refactored for readability, although I'm not sure it really
        // makes sense now that it's clearer! Are you sure this is what
        // you want?
        var page = await QueryWebPage(v);
        Queue.Add(new Query(page, false);
    }

    Queue.CompleteAdding();
    await ConsumerTask;
    await ParseAll(true);
}

メソッドReinitも変更する必要があります。現在、ConsumerTaskは基本的にほぼすぐに完了し、voidを返す別のConsume非同期メソッドであるためすぐに戻ります。

正直なところ、非同期/待機を適切に理解していないと、取得したものは非常に複雑に見えます。私はasync/awaitについてもっと読み、それからおそらく最初から始めます。私はあなたがこれをはるかに簡単にすることができると強く思う。また、生産者/消費者シナリオを単純化するように設計されたTPLデータフローを確認することもできます。

于 2013-03-15T20:02:24.883 に答える