A task is sent from the server to the client, but it arrives to the client as null. The client accepts the first few tasks and they are not null, they are normally transmitted. But at some point it is null again. Here is the link to the repository with the full project: https://github.com/DarkByte2015/CoursedWork_v5 . I would be very grateful if someone can help. Minimally reproducible example:

namespace CWServer.ViewModels { public class MainViewModel : ViewModelBase { private JobService ServiceInstance; private ServiceHost ServiceHost; private Queue<ClientJob> Jobs = new Queue<ClientJob>(); private int JobCount; private List<double> Results = new List<double>(); public ObservableCollection<ClientViewModel> Clients { get; } = new ObservableCollection<ClientViewModel>(); public MainViewModel() { StartServer(); SolveAsync(); } private void StartServer() { ServiceInstance = new JobService(); ServiceInstance.ClientConnected += ClientConnected; ServiceInstance.ClientDisconnected += ClientDisconnected; ServiceInstance.ClientCalculated += ClientCalculated; ServiceHost = new ServiceHost(ServiceInstance); ServiceHost.Open(); } private void StopServer() { Clients.AsParallel().ForAll(c => c.DisconnectAsync()); ServiceHost.Close(); Dispatch(() => Clients.Clear()); } private async void SolveAsync() => await Task.Run(() => Solve()); private void Solve() { foreach (var job in ClientJob.Distribute()) Jobs.Enqueue(job); JobCount = Jobs.Count; Clients.AsParallel().ForAll(c => TryGiveJob(c)); while (LockingCall.Invoke(() => Results.Count != JobCount, JobCount)) ; Results.Clear(); } private void ClientConnected(object sender, ClientChangeStateEventArgs e) { var client = new ClientViewModel(e.Callback); client.ClientAborted += ClientAborted; Dispatch(() => Clients.Add(client)); TryGiveJob(client); } private void ClientDisconnected(object sender, ClientChangeStateEventArgs e) { var client = Clients.First(c => c.Callback == e.Callback); RemoveClient(client); } private void ClientAborted(object sender, ClientAbortedEventArgs e) { var client = sender as ClientViewModel; RemoveClient(client); } private void ClientCalculated(object sender, ClientCalculatedEventArgs e) { var client = Clients.First(c => c.Callback == e.Callback); client.EndCalculationAsync(); LockingCall.Invoke(() => Results.Add(1.0), sender); TryGiveJob(client); } private bool TryGiveJob(ClientViewModel client) { if (LockingCall.Invoke(() => Jobs.Count > 0, client)) { if (LockingCall.Invoke(() => Jobs.Peek() == null, client)) Debugger.Launch(); var job = LockingCall.Invoke(() => Jobs.Dequeue(), client); client.BeginCalculationAsync(job); return true; } else return false; } private void RemoveClient(ClientViewModel client) { if (client.IsCalculating) LockingCall.Invoke(() => Jobs.Enqueue(client.Job), JobCount); Dispatch(() => Clients.Remove(client)); } private void Dispatch(Action action) => Application.Current.Dispatcher.Invoke(action); } } namespace CWServer.ViewModels { public class ClientViewModel : ViewModelBase { public event EventHandler<ClientAbortedEventArgs> ClientAborted; public IClientCallback Callback { get; } public ClientJob Job { get; private set; } public bool IsCalculating { get { return Job != null; } } public ClientViewModel(IClientCallback callback) { Callback = callback; } public async void DisconnectAsync() { try { await Task.Run(() => Callback.OnServerIsStopped()); } catch (Exception e) { Debug.WriteLine(e.Message); } } public async void BeginCalculationAsync(ClientJob job) { if (job == null) Debug.WriteLine("BeginCalculationAsync: job == null"); Job = job; try { await Task.Run(() => Callback.OnGiveJob(Job)); } catch (Exception e) { Debug.WriteLine(e.Message); var args = new ClientAbortedEventArgs(e, Job); ClientAborted?.Invoke(this, args); } } public async void EndCalculationAsync() => await Task.Run((Action)EndCalculation); private void EndCalculation() { Job = null; } } } [DataContract] public class ClientJob { public static ClientJob[] Distribute() => Enumerable.Repeat<ClientJob>(null, 20000).Select(j => new ClientJob()).ToArray(); } namespace CWServer.ServiceContracts { [ServiceContract] public interface IClientCallback { [OperationContract(IsOneWay = true)] void OnGiveJob(ClientJob job); [OperationContract(IsOneWay = true)] void OnServerIsStopped(); } } namespace CWServer.ServiceContracts { [ServiceContract(CallbackContract = typeof(IClientCallback))] public interface IJobService { [OperationContract] void Connect(); [OperationContract] void SetResult(ClientJob job); [OperationContract] void Disconnect(); } } namespace CWServer.ServiceContracts { [ServiceBehavior(InstanceContextMode = InstanceContextMode.Single, ConcurrencyMode = ConcurrencyMode.Reentrant, IncludeExceptionDetailInFaults = true)] public class JobService : IJobService { public event EventHandler<ClientChangeStateEventArgs> ClientConnected; public event EventHandler<ClientChangeStateEventArgs> ClientDisconnected; public event EventHandler<ClientCalculatedEventArgs> ClientCalculated; void IJobService.Connect() { var callback = OperationContext.Current.GetCallbackChannel<IClientCallback>(); var address = GetClientAddress(); var args = new ClientChangeStateEventArgs(callback, address); ClientConnected?.Invoke(this, args); } void IJobService.Disconnect() { var callback = OperationContext.Current.GetCallbackChannel<IClientCallback>(); var address = GetClientAddress(); var args = new ClientChangeStateEventArgs(callback, address); ClientDisconnected?.Invoke(this, args); } void IJobService.SetResult(ClientJob job) { var callback = OperationContext.Current.GetCallbackChannel<IClientCallback>(); var args = new ClientCalculatedEventArgs(callback, job); ClientCalculated?.Invoke(this, args); } private string GetClientAddress() { var context = OperationContext.Current; var prop = context.IncomingMessageProperties; var endpoint = prop[RemoteEndpointMessageProperty.Name] as RemoteEndpointMessageProperty; return endpoint.Address; } } } namespace CWClient { class Program : IJobServiceCallback { private JobServiceClient client; public async void OnGiveJob(ClientJob job) { Debug.Assert(job != null); await Task.Delay(1000); await client.SetResultAsync(job); } public void OnServerIsStopped() { Console.WriteLine("Stopped."); } static void Main(string[] args) { var program = new Program(); var context = new InstanceContext(program); program.client = new JobServiceClient(context); // строку ΠΏΠΎΠ΄ΠΊΠ»ΡŽΡ‡Π΅Π½ΠΈΡ оставим Π² ΠΊΠΎΠ½Ρ„ΠΈΠ³-Ρ„Π°ΠΉΠ»Π΅ program.client.Connect(); Console.ReadLine(); } } } 

    1 answer 1

    You have a race between BeginCalculationAsync and EndCalculation . The fact is that in the ClientCalculated method ClientCalculated you first call the second method, then the first. But the second method is called asynchronously and works in parallel with the first!

    So it turns out that while BeginCalculationAsync is BeginCalculationAsync executed - the Job property will quietly become null in the EndCalculation method!

    Why did you even make the EndCalculation method asynchronous? There is nothing in it that could require a new flow. Remove the EndCalculationAsync method, make EndCalculation open - and everything will work as planned.


    PS You too often use Task.Run , and from here there are problems that you try to close with Dispatcher and LockingCall . I offer you an alternative: you can write a server without using threads at all!

    1. Make the callback methods in the IClientCallback interface return Task - then you do not need to create a separate thread for them.

    2. Add the UseSynchronisationContext setting to the UseSynchronisationContext attribute - then WCF will call the methods of your service already in the UI thread.

    3. Remove all LockingCall , Dispatch , Task.Run and AsParallel calls from the code altogether - use only the await operator. Accordingly, most of the methods will need to be remade to asynchronous (but not to async void - they must be async Task !). Then your program will stop throwing up surprises for you, which you then deal with for a few days.

    Probably, you will have difficulty with the Solve method, namely with the expectation when all the tasks will end.

    The easiest way (for you) is to use asynchronous active wait:

     while (Results.Count != JobCount) await Task.Delay(100); 

    A delay of 100 milliseconds is sufficient so that, on the one hand, the program does not slow down due to the cycle - and on the other hand, the user does not notice it.

    A more beautiful solution can be achieved by using TaskCompletionSource to wait for an event:

     // Π² ΠΌΠ΅Ρ‚ΠΎΠ΄Π΅ Solve doneEvent = new TaskCompletionSource<object>(); await doneEvent.Task; // ... // Π² ΠΌΠ΅Ρ‚ΠΎΠ΄Π΅ ClientCalculated if (Results.Count == JobCount) doneEvent.SetResult(null);