Entries:
Comments:
Posts:

Loading User Information from Channel 9

Something went wrong getting user information from Channel 9

Latest Achievement:

Loading User Information from MSDN

Something went wrong getting user information from MSDN

Visual Studio Achievements

Latest Achievement:

Loading Visual Studio Achievements

Something went wrong getting the Visual Studio Achievements

Discussions

William Kempf wkempf
  • async and lock

    @BitFlipper: The async/await functionality does far more than the code you showed using ThreadManager. It's an important advancement even in non-UI code.

  • async and lock

    BTW, I just tested with actual file I/O, and at least that simulated test did have horrible performance characteristics.

    Sync: 00:00:00.0157656 which is 0.000016ms per op
    Async: 00:00:22.1649903 which is 0.022165ms per op

    So, I really don't have confidence that any of this is going to work for you, but I'm still not willing to jump to conclusions. You really do have to profile the real code.

  • async and lock

    @cheong: No, that's going to have several problems, including those he's already pointed out. You shouldn't mix async with blocking APIs.

    , BitFlipper wrote

    *snip*

    The thing is that there is no deterministic behavior, so one would be guessing and hoping that whatever you call into happens synchronously.

    You are absolutely right that there's no deterministic behavior here, but I don't fully agree with the rest of your conclusion. Again, I wouldn't jump to any conclusions until I'd profiled.

    *snip*

    Yea that is why I'm having problems. Everything is based on this flawed idea that only the UI can possibly initiate calls into these APIs, and hence the overzealous asyncing of all APIs. The code I'm writing has nothing to do with the UI and these are dedicated background threads. Forcing this async pattern from top to bottom is causing other patterns to break.

    I think you missed the point here. Being on the UI thread increases the danger, but being on a background thread doesn't make it safe to switch in and out of async. With very careful attention to what you're doing, you can actually do this. But it's complicated enough that I'd just warn against doing this. It's far easier and safer to just remain "async all the way", as they say.

    *snip*

    I wasn't trying to alleviate the overhead, I was trying to make the code thread safe by not breaking locks that are held earlier in the stack. I'm painfully aware of the latencies that are added even when using Wait. Since the API I need to use has only an async version available, I'm forced down this path. It once again comes down to trying to force the UI threading model into everything else.

    It's not really an issue of "breaking locks". If that's all it were, there'd be ways to ensure the locks aren't broken. No, the issue is that locks were never intended to be used that way (i.e. held for an indeterminate amount of time while an asynchronous operation is being performed). It's just not safe to do that. Even holding a lock for longer periods of time can sometimes be problematic, so holding the lock even while doing synchronous I/O isn't necessarily the best way to architect this.

    As I mentioned I can probably move the file IO code out of the audio processing threads but I wonder what will happen when async ends up being the only choice (it seems we are moving in that direction).

    BTW I like your idea of providing sync and async versions of all APIs but have the sync versions throw exceptions if they are called from a UI thread. We already have these types of invalid cross-thread checks when you make calls into controls. And the sync versions should not be thin wrappers around the async versions, like this:

    *snip*

    Yeah, like I said, I get why Microsoft did this. Currently, WinRT is mostly just a UI framework, and their discouraging (actually, preventing) all of the bad habits developers have had for decades in that domain. However, just because I get that, doesn't mean I fully agree with the decision. Yes, make async pervasive, but don't make it the only choice, as there are times where async is not a good choice. So, don't confuse my attempts to find solutions for you as an endorsement of the current state. :)

  • async and lock

    @BitFlipper: It all depends. Like I said, async I/O performs synchronously whenever it can. If you're reading from disk and the data is already in the cache/buffer, it's likely to run synchronously. The implementation should, in general, do everything it possibly can to ensure things do run synchronously, though obviously it won't always. However, I'd suspect that when comparing to synchronous I/O you'll see a difference only in extreme cases. Of course, your latency requirements may actually put this into one of those extreme cases. I honestly don't know. I think you'd really have to profile real code to know.

    As for your example "keeping everything synchronous", there's several issues I can point out. First is minor... you don't need to Wait() before you return task.Result. Using task.Result will block if there's no result yet. Far more important, though, is this is the type of code likely to cause you deadlocks, especially on the UI thread. http://msdn.microsoft.com/en-us/magazine/jj991977.aspx

    More importantly, switching from async to sync by blocking on the result does NOTHING to alleviate the overhead of async. I really wouldn't recommend doing this.

  • async and lock

    , BitFlipper wrote

    @wkempf: I changed DoSomething as you suggested, and here are my results:

    At this point I'm just trying to understand what overhead/latency is added when you make an async call.

    That depends on entirely on what the async call does and how it does it. Does it create a thread? Does it use the thread pool? Does it use events? Does it do async I/O? Something else? Every one of those will have radically different overhead.

  • async and lock

    Here's a more realistic test.

    void Main()
    {
        MainAsync().Wait();
    }
    
    public static async Task MainAsync()
    {
        var iterations = 1000000;
        var stream = new MemoryStream();
        for (int i = 0; i < iterations; ++i)
        {
            stream.WriteByte(42);
        }
        
        stream.Position = 0L;
        TestSync(stream, iterations);
    
        stream.Position = 0L;
        var time = TestSync(stream, iterations);
        DumpResults("Sync", time, iterations);
        
        stream.Position = 0L;
        TestAsync(stream, iterations);
    
        stream.Position = 0L;
        time = await TestAsync(stream, iterations);
        DumpResults("Async", time, iterations);
    }
    
    // Define other methods and classes here
    
    public static void DumpResults(string label, TimeSpan time, int iterations)
    {
        (label + ": " + time.ToString() + " which is " + (time.TotalSeconds / iterations * 1000.0).ToString("F6") + "ms per op").Dump();
    }
    
    public static TimeSpan TestSync(Stream stream, int iterations)
    {
        var result = 0L;
        var sw = Stopwatch.StartNew();
        for (int i = 0; i < iterations; ++i)
        {
            result += stream.ReadByte();
        }
        
        return sw.Elapsed;
    }
    
    public static async Task<TimeSpan> TestAsync(Stream stream, int iterations)
    {
        var result = 0L;
        var sw = Stopwatch.StartNew();
        var buffer = new byte[1];
        for (int i = 0; i < iterations; ++i)
        {
            await stream.ReadAsync(buffer, 0, 1);
            result += buffer[0];
        }
        
        return sw.Elapsed;
    }

    I ran this in LinqPad, and the results I get are:

    Sync: 00:00:00.0132225 which is 0.000013ms per op
    Async: 00:00:00.0586029 which is 0.000059ms per op

    Using a MemoryStream is still not fully accurate. Using a real file, or network I/O, or whatever you're doing to get the audio stream, is going to have a lot more overhead. The MemoryStream test is likely always operating synchronously here. However, you do see how big a difference it can make what it is your doing asynchronously and how your doing it.

  • async and lock

    public static async Task<int> DoSomethingAsync()
    {
        await Task.Yield();
        return 42;
    }

    The numbers I got could be radically different because I'm on a different machine. I'm also running in LinqPad, and I "primed the pump", something you didn't do. However, I wouldn't expect the numbers to be orders of magnitude different, which they are.

    Task.Run has overhead in scheduling something to run on the thread pool. Async I/O doesn't, and in fact many times those operations will actually execute synchronously, executing asynchronously only when there's no data available. If you really want to measure, you should do so with a test that actually does async I/O in the three variations you're testing. Then you'll be testing like for like. I suspect you'll see real differences, but the differences may not be so great as to be problematic for you. Hard for me to guess there, as I've never had to deal with audio latency like this. I just know that benchmarking isn't easy to do, especially when you're dealing with async operations. You really need to profile actual running code and not something simulated like this. There's also ways to optimize various things, so it's possible even if you fail the first time that you could still get the latency down to something acceptable. Or, maybe you can't. There is overhead to async.

     

  • async and lock

    I'm not sure your test is very accurate. For instance, if I do "await Task.Yield(); return 42;" instead I get "Time: 00:00:04.6713406which is 0.004671ms per op", which is much closer to your Sync time. Task.Yield() isn't accurate, either, but it's probably much closer to what you'll get with an async I/O operation then Task.Run() is. There is definite overhead in anything async, but the amount of overhead can differ greatly depending on what it is that you're doing and how you do it.

    That said, while I understand why they did it, I agree with you. WinRT really should provide both async and sync variations on most APIs. A compromise might be to have any synchronous APIs called on the "UI thread" throw an Exception. (UI thread is in quotes, because there's not necessarily a single UI thread.)

  • async and lock

    It sounds like you're working on something where timing may be critical, so what I'm about to suggest may not work. However, it does answer your question.

    Blocking is not the only way to synchronize access. For instance, you could use an Actor model where data is isolated to be accessed from a single thread through message passing. Another approach is to use async synchronization primitives. .NET includes one: the SemaphoreSlim in .NET 4.5 has WaitAsync methods. You can also build your own: http://blogs.msdn.com/b/pfxteam/archive/2012/02/12/10266988.aspx. These synchronization primitives provide the same conceptual operations as traditional synchronization primitives, but they do so without blocking threads. So the AsyncLock behaves much like the lock statement in that it ensures mutually exclusive access to the shared data, but it does so without blocking. Toub's series of blog posts builds several other async synchronization primitives as well.

  • Cortana on other platforms

    @Blue Ink: With Qi charging, I don't find it to be a big deal. Take your watch off and drop it on a plate and you're done. Of course, that "take your watch off" bit gets me, personally. I know lots of people do that, but I don't, generally, and doing it every night would reduce the lifespan of the band.