The Quick-Step Floors rider crossed the line in a time of 3:19:57 after 155km of racing.
With 24.5km left, Team Sky’s Wout Poels, BMC Racing Team’s Alessandro De Marchi, Astana Pro Team’s Luis Leon Sanchez and Israel Cycling Academy’s Ben Hermans were leading the race.
The peloton was 25 seconds behind them, which made the possibility of a sprint finish uncertain.
As the main bunch closed in on the leaders, De Marchi launched an attack and Sanchez and Hermans were soon on his heels, before the latter was dropped.
Hermans was eventually caught by the peloton, which still had a 25-second gap to the leaders with 18km left.
With 11.5km remaining, De Marchi and Sanchez slipped back into the bunch and they were all together before Trek-Segafredo’s Gianluca Brambilla attacked.
LottoNL’s Robert Gesink and Bora-Hansgrohe’s Cesare Benedetti joined him, as well as Quick-Step Floors’ Zdenek Stybar and Team Sky’s Sergio Henao a few seconds later.
Stybar executed an attack which was almost immediately pulled back. This was followed by one from Katusha-Alpecin’s Maurits Lammertink, who soon had a seven-second gap with 4.2km left. He was eventually caught 3km from the finish.
LottoNL-Jumbo’s Danny van Poppel sprinted for the finish and was immediately followed by Viviani, who crossed the line in first place.
The stage winner today became the first Italian in the last 13 years to claim victory in four stages at a single Giro.
“We knew it could be a really hard stage, because after the time trial and rest day, people will have recovered a bit of energy,” he said.
“For sure if we are having a bunch sprint, I said to my guys, organize a really good lead-out. We did perfect – it was amazing today.”
LottoNL-Jumbo’s George Bennett came in second, with Bahrain–Merida’s Niccolo Bonifazio third.
Tomorrow’s stage kicks off in Abbiategrasso, with riders crossing the finish line 196km later in Prato Nevoso.