2016-08-07 14 views
1

私は現在、F#で基本的にアクティブなTCP接続を格納している小さなサーバータイプをプログラミングしています。そのループでは、それは新しい接続をチェックし、それはまた次のコードを使用して、現在の接続がまだ生きていることを確認しますInvalidOperationExceptionコレクションが変更されましたが、反復中にコレクションを変更しませんでしたか?

これがうまく動作するかどうかを知るためには
type TCPListenerServer(discoveryPort:int) = 
    let server = new TcpListener (IPAddress.Loopback, discoveryPort) 

    let activeConnections = new List<TcpClient>() 
    let cancellationToken = new CancellationTokenSource() 

    let connectionIsStillActive (client:TcpClient) = 
     let ipProperties = IPGlobalProperties.GetIPGlobalProperties() 
     let allTcpConnections = ipProperties.GetActiveTcpConnections() 
     let relevantTcpConnections = Array.filter (fun (connectionInfo:TcpConnectionInformation) -> 
      (connectionInfo.LocalEndPoint = (client.Client.LocalEndPoint :?> IPEndPoint)) && (connectionInfo.RemoteEndPoint = (client.Client.RemoteEndPoint :?> IPEndPoint))) allTcpConnections 

     try 
      let stateOfConnection = (Array.get relevantTcpConnections 0).State 
      match stateOfConnection with 
      | TcpState.Established -> 
       true 
      | _ -> 
       false 
     with 
     | :? System.IndexOutOfRangeException as ex -> 
      false 

    let rec loop (pendingConnection:Task<TcpClient>) = async {    
     let newPendingConnection, client = 
      match pendingConnection.Status with 
      | TaskStatus.Created | TaskStatus.WaitingForActivation | TaskStatus.WaitingToRun 
      | TaskStatus.WaitingForChildrenToComplete | TaskStatus.Running -> 
       (None, None) 
      | TaskStatus.Faulted -> 
       let result = pendingConnection.Exception 
       raise (new System.NotImplementedException()) 
      | TaskStatus.Canceled -> 
       raise (new System.NotImplementedException()) 
      | TaskStatus.RanToCompletion -> 
       let connectionTask = server.AcceptTcpClientAsync() 
       (Some connectionTask, Some pendingConnection.Result) 
      | _ -> 
       raise (new System.NotImplementedException()) 

     // Add the new client to the list 
     Option.iter (fun c -> activeConnections.Add c) client 

     // Switch the new pending connection if there is one 
     let connectionAttempt = defaultArg newPendingConnection pendingConnection 

     // Check that the connections are still alive 
     let connectionsToRemove = Seq.filter (fun (connection:TcpClient) -> not (connectionIsStillActive connection)) activeConnections 
     Seq.iter (fun connection -> activeConnections.Remove connection |> ignore) connectionsToRemove // <-- Exception happens here 


     Async.Sleep 1000 |> Async.RunSynchronously 
     return! loop connectionAttempt 
    } 

    member x.Start() = 
     if not cancellationToken.Token.CanBeCanceled 
     then 
      raise (new System.Exception("Cancellation token cannot be used to cancel server loop task.")) 

     try 
      server.Start() 
      let connectionTask = server.AcceptTcpClientAsync() 
      Async.Start (loop connectionTask, cancellationToken.Token) 
     with 
     | :? SocketException as ex -> 
      server.Stop() 
      raise ex 

    member x.Stop() = 
     cancellationToken.Cancel() 
     Async.Sleep 2000 |> Async.RunSynchronously 
     server.Stop() 
     activeConnections.Clear() 

    member x.ActiveConnections = 
     activeConnections 

、私はこの単純なユニット・テストを実施しています

のSystem.InvalidOperationException:コレクションカ月だった残念ながら、私はこのテストを実行したときに、私は、クライアント上でClose()を呼び出した後、私は次の例外を取得し

[<TestMethod>] 
[<TestCategory(Networking)>] 
member x.``Start Server, Client Connects, then Disconnects``() = 
    let server = new TCPListenerServer(44000) 
    server.Start() 

    let client = createClientAndConnect 44000 

    Async.Sleep 5000 |> Async.RunSynchronously 

    client.GetStream().Close() 
    client.Close() 

    Async.Sleep 5000 |> Async.RunSynchronously 

    Assert.IsTrue(server.ActiveConnections.Count = 0, "There are still connections in the server's active connections list.") 

    cleanupTest server (Some [client]) 

異なる。列挙操作が実行されないことがあります。 [email protected] [T](FSharpFunc 2 f, IEnumerator 1 EのSystem.Collections.Generic.List 1.Enumerator.MoveNextRare() at System.Collections.Generic.List 1.Enumerator.MoveNext() でSystem.ThrowHelper.ThrowInvalidOperationException(ExceptionResourceリソース) において、 FSharpRef 1 started, Unit unitVar0) at [email protected]ctions-IEnumerator-MoveNext() at Microsoft.FSharp.Collections.SeqModule.Iterate[T](FSharpFunc 2アクション、IEnumerableを1 source) at [email protected](Unit unitVar) in E:\Documents\Source Control Projects\WiDroid\Core.Networking\TCPListenerServer.fs:line 61 at [email protected](AsyncParams 1引数)

私はあなたがそれを反復しながら、コレクションを変更するときに、この例外が発生したことを理解しています。しかし私は私が見ることができるものから自分のコードで直接行っていません。なぜこの例外が発生するのだろうと思っていますか?

+2

私はF#はわかりませんが、私の推測では、非アクティブなソケットクリーンアップコードです: 'let connectionsToRemove = Seq.filter(fun(connection:TcpClient) - > not(connectionIsStillActive connection))activeCo Seq.iter(接続を楽しむ - > activeConnections.接続を削除する|>無視する)connectionsToRemove'。私は 'connectionsToRemove'を反復処理することは実際には' activeConnections'を繰り返し処理すると思っています。これは反復処理中にアイテムを 'activeConnections'から削除すると問題を引き起こします。 – wablab

+0

@wablab例外は、その場所で起こります: '' Seq.iter(fun connection - > activeConnections.Remove connection |> ignore)connectionsToRemove''実際に。私は2つのリストがどのように結びついているのかわかりませんし、なぜ1つを反復するのが他のものを反復するのでしょうか? – Choub890

+0

あなたの問題を[最小限で完全であり、検証可能な例](http://stackoverflow.com/help/mcve)に絞り込むために、バイナリ検索の使用を検討してください。多くの場合、私の経験では、それを実行する単なる行為は、あなた自身の問題のトラブルシューティングを可能にするでしょう。 –

答えて

3
let connectionsToRemove = Seq.filter (fun (connection:TcpClient) -> not (connectionIsStillActive connection)) activeConnections 
Seq.iter (fun connection -> activeConnections.Remove connection |> ignore) connectionsToRemove 

私はこの問題は、配列(IEnumerable<T>の別名)を返しSeq.filter、そこにあると思います。 それは何らかのデータコンテナ(配列やリストのようなもの)ではなく、むしろ「ある時点でアイテムを与える計算」である。

つまり、Seq.iterを実行すると、を「計算」してactiveConnectionsを効果的に反復処理する必要があります。しかし、同じSeq.iterでは、反復されたコレクションからアイテムを削除することを頼みます。つまり、エラーです。

あなたはここのC#に精通している場合、コード外観ラインを可能性があり、その後、あなたは明らかにあなたが多分List<T>.RemoveAll

への呼び出しでそれを置き換えることができ、問題

foreach (TcpClient connection in activeConnections) 
{ 
    if (!connectionIsStillActive connection) 
    { 
     activeConnections.Remove (connection); 
    } 
} 

を見たものです

//doesn't seems to work directly (can't convert (TcpClient -> bool) to Predicate<TcpClient>) 
//activeConnections.RemoveAll (not << connectionIsStillActive) 

//this would work though 
//activeConnections.RemoveAll (Predicate (not << connectionIsStillActive)) 

activeConnections.RemoveAll (fun connection -> not <| connectionIsStillActive connection) 
|> ignore 
+0

知っておいてよかった!私はその根底にある論理を見ていませんでした。あなたのソリューションは私に次のようなエラーを出します: ''この式はSystem.Predicateタイプがであると予想されましたが、ここにはbool型があります。理由を理解できないようですか? – Choub890

+1

次のような "長い"方法でも動作します: '' activeConnections.RemoveAll(fun connection - > not(connectionIsStillActive connection))|> ignore'' – Choub890

+2

F#関数が.Netに直接変換できないようですデリゲート(または少なくとも "明示的に定義されていない"時)ではないようなこと^ ^あなたは修正したように保つことができます、私は答えを編集します – Sehnsucht

関連する問題